##// END OF EJS Templates
pytype: stop excluding changegroup.py...
Matt Harbison -
r49304:8843c9a8 default
parent child Browse files
Show More
@@ -1,1980 +1,1981 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullrev,
17 nullrev,
18 short,
18 short,
19 )
19 )
20 from .pycompat import open
20 from .pycompat import open
21
21
22 from . import (
22 from . import (
23 error,
23 error,
24 match as matchmod,
24 match as matchmod,
25 mdiff,
25 mdiff,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 requirements,
28 requirements,
29 scmutil,
29 scmutil,
30 util,
30 util,
31 )
31 )
32
32
33 from .interfaces import repository
33 from .interfaces import repository
34 from .revlogutils import sidedata as sidedatamod
34 from .revlogutils import sidedata as sidedatamod
35 from .revlogutils import constants as revlog_constants
35 from .revlogutils import constants as revlog_constants
36 from .utils import storageutil
36 from .utils import storageutil
37
37
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
42
42
43 LFS_REQUIREMENT = b'lfs'
43 LFS_REQUIREMENT = b'lfs'
44
44
45 readexactly = util.readexactly
45 readexactly = util.readexactly
46
46
47
47
48 def getchunk(stream):
48 def getchunk(stream):
49 """return the next chunk from stream as a string"""
49 """return the next chunk from stream as a string"""
50 d = readexactly(stream, 4)
50 d = readexactly(stream, 4)
51 l = struct.unpack(b">l", d)[0]
51 l = struct.unpack(b">l", d)[0]
52 if l <= 4:
52 if l <= 4:
53 if l:
53 if l:
54 raise error.Abort(_(b"invalid chunk length %d") % l)
54 raise error.Abort(_(b"invalid chunk length %d") % l)
55 return b""
55 return b""
56 return readexactly(stream, l - 4)
56 return readexactly(stream, l - 4)
57
57
58
58
59 def chunkheader(length):
59 def chunkheader(length):
60 """return a changegroup chunk header (string)"""
60 """return a changegroup chunk header (string)"""
61 return struct.pack(b">l", length + 4)
61 return struct.pack(b">l", length + 4)
62
62
63
63
64 def closechunk():
64 def closechunk():
65 """return a changegroup chunk header (string) for a zero-length chunk"""
65 """return a changegroup chunk header (string) for a zero-length chunk"""
66 return struct.pack(b">l", 0)
66 return struct.pack(b">l", 0)
67
67
68
68
69 def _fileheader(path):
69 def _fileheader(path):
70 """Obtain a changegroup chunk header for a named path."""
70 """Obtain a changegroup chunk header for a named path."""
71 return chunkheader(len(path)) + path
71 return chunkheader(len(path)) + path
72
72
73
73
74 def writechunks(ui, chunks, filename, vfs=None):
74 def writechunks(ui, chunks, filename, vfs=None):
75 """Write chunks to a file and return its filename.
75 """Write chunks to a file and return its filename.
76
76
77 The stream is assumed to be a bundle file.
77 The stream is assumed to be a bundle file.
78 Existing files will not be overwritten.
78 Existing files will not be overwritten.
79 If no filename is specified, a temporary file is created.
79 If no filename is specified, a temporary file is created.
80 """
80 """
81 fh = None
81 fh = None
82 cleanup = None
82 cleanup = None
83 try:
83 try:
84 if filename:
84 if filename:
85 if vfs:
85 if vfs:
86 fh = vfs.open(filename, b"wb")
86 fh = vfs.open(filename, b"wb")
87 else:
87 else:
88 # Increase default buffer size because default is usually
88 # Increase default buffer size because default is usually
89 # small (4k is common on Linux).
89 # small (4k is common on Linux).
90 fh = open(filename, b"wb", 131072)
90 fh = open(filename, b"wb", 131072)
91 else:
91 else:
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
93 fh = os.fdopen(fd, "wb")
93 fh = os.fdopen(fd, "wb")
94 cleanup = filename
94 cleanup = filename
95 for c in chunks:
95 for c in chunks:
96 fh.write(c)
96 fh.write(c)
97 cleanup = None
97 cleanup = None
98 return filename
98 return filename
99 finally:
99 finally:
100 if fh is not None:
100 if fh is not None:
101 fh.close()
101 fh.close()
102 if cleanup is not None:
102 if cleanup is not None:
103 if filename and vfs:
103 if filename and vfs:
104 vfs.unlink(cleanup)
104 vfs.unlink(cleanup)
105 else:
105 else:
106 os.unlink(cleanup)
106 os.unlink(cleanup)
107
107
108
108
109 class cg1unpacker(object):
109 class cg1unpacker(object):
110 """Unpacker for cg1 changegroup streams.
110 """Unpacker for cg1 changegroup streams.
111
111
112 A changegroup unpacker handles the framing of the revision data in
112 A changegroup unpacker handles the framing of the revision data in
113 the wire format. Most consumers will want to use the apply()
113 the wire format. Most consumers will want to use the apply()
114 method to add the changes from the changegroup to a repository.
114 method to add the changes from the changegroup to a repository.
115
115
116 If you're forwarding a changegroup unmodified to another consumer,
116 If you're forwarding a changegroup unmodified to another consumer,
117 use getchunks(), which returns an iterator of changegroup
117 use getchunks(), which returns an iterator of changegroup
118 chunks. This is mostly useful for cases where you need to know the
118 chunks. This is mostly useful for cases where you need to know the
119 data stream has ended by observing the end of the changegroup.
119 data stream has ended by observing the end of the changegroup.
120
120
121 deltachunk() is useful only if you're applying delta data. Most
121 deltachunk() is useful only if you're applying delta data. Most
122 consumers should prefer apply() instead.
122 consumers should prefer apply() instead.
123
123
124 A few other public methods exist. Those are used only for
124 A few other public methods exist. Those are used only for
125 bundlerepo and some debug commands - their use is discouraged.
125 bundlerepo and some debug commands - their use is discouraged.
126 """
126 """
127
127
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
129 deltaheadersize = deltaheader.size
129 deltaheadersize = deltaheader.size
130 version = b'01'
130 version = b'01'
131 _grouplistcount = 1 # One list of files after the manifests
131 _grouplistcount = 1 # One list of files after the manifests
132
132
133 def __init__(self, fh, alg, extras=None):
133 def __init__(self, fh, alg, extras=None):
134 if alg is None:
134 if alg is None:
135 alg = b'UN'
135 alg = b'UN'
136 if alg not in util.compengines.supportedbundletypes:
136 if alg not in util.compengines.supportedbundletypes:
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
138 if alg == b'BZ':
138 if alg == b'BZ':
139 alg = b'_truncatedBZ'
139 alg = b'_truncatedBZ'
140
140
141 compengine = util.compengines.forbundletype(alg)
141 compengine = util.compengines.forbundletype(alg)
142 self._stream = compengine.decompressorreader(fh)
142 self._stream = compengine.decompressorreader(fh)
143 self._type = alg
143 self._type = alg
144 self.extras = extras or {}
144 self.extras = extras or {}
145 self.callback = None
145 self.callback = None
146
146
147 # These methods (compressed, read, seek, tell) all appear to only
147 # These methods (compressed, read, seek, tell) all appear to only
148 # be used by bundlerepo, but it's a little hard to tell.
148 # be used by bundlerepo, but it's a little hard to tell.
149 def compressed(self):
149 def compressed(self):
150 return self._type is not None and self._type != b'UN'
150 return self._type is not None and self._type != b'UN'
151
151
152 def read(self, l):
152 def read(self, l):
153 return self._stream.read(l)
153 return self._stream.read(l)
154
154
155 def seek(self, pos):
155 def seek(self, pos):
156 return self._stream.seek(pos)
156 return self._stream.seek(pos)
157
157
158 def tell(self):
158 def tell(self):
159 return self._stream.tell()
159 return self._stream.tell()
160
160
161 def close(self):
161 def close(self):
162 return self._stream.close()
162 return self._stream.close()
163
163
164 def _chunklength(self):
164 def _chunklength(self):
165 d = readexactly(self._stream, 4)
165 d = readexactly(self._stream, 4)
166 l = struct.unpack(b">l", d)[0]
166 l = struct.unpack(b">l", d)[0]
167 if l <= 4:
167 if l <= 4:
168 if l:
168 if l:
169 raise error.Abort(_(b"invalid chunk length %d") % l)
169 raise error.Abort(_(b"invalid chunk length %d") % l)
170 return 0
170 return 0
171 if self.callback:
171 if self.callback:
172 self.callback()
172 self.callback()
173 return l - 4
173 return l - 4
174
174
175 def changelogheader(self):
175 def changelogheader(self):
176 """v10 does not have a changelog header chunk"""
176 """v10 does not have a changelog header chunk"""
177 return {}
177 return {}
178
178
179 def manifestheader(self):
179 def manifestheader(self):
180 """v10 does not have a manifest header chunk"""
180 """v10 does not have a manifest header chunk"""
181 return {}
181 return {}
182
182
183 def filelogheader(self):
183 def filelogheader(self):
184 """return the header of the filelogs chunk, v10 only has the filename"""
184 """return the header of the filelogs chunk, v10 only has the filename"""
185 l = self._chunklength()
185 l = self._chunklength()
186 if not l:
186 if not l:
187 return {}
187 return {}
188 fname = readexactly(self._stream, l)
188 fname = readexactly(self._stream, l)
189 return {b'filename': fname}
189 return {b'filename': fname}
190
190
191 def _deltaheader(self, headertuple, prevnode):
191 def _deltaheader(self, headertuple, prevnode):
192 node, p1, p2, cs = headertuple
192 node, p1, p2, cs = headertuple
193 if prevnode is None:
193 if prevnode is None:
194 deltabase = p1
194 deltabase = p1
195 else:
195 else:
196 deltabase = prevnode
196 deltabase = prevnode
197 flags = 0
197 flags = 0
198 protocol_flags = 0
198 protocol_flags = 0
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
200
200
201 def deltachunk(self, prevnode):
201 def deltachunk(self, prevnode):
202 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
202 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
203 l = self._chunklength()
203 l = self._chunklength()
204 if not l:
204 if not l:
205 return {}
205 return {}
206 headerdata = readexactly(self._stream, self.deltaheadersize)
206 headerdata = readexactly(self._stream, self.deltaheadersize)
207 header = self.deltaheader.unpack(headerdata)
207 header = self.deltaheader.unpack(headerdata)
208 delta = readexactly(self._stream, l - self.deltaheadersize)
208 delta = readexactly(self._stream, l - self.deltaheadersize)
209 header = self._deltaheader(header, prevnode)
209 header = self._deltaheader(header, prevnode)
210 node, p1, p2, deltabase, cs, flags, protocol_flags = header
210 node, p1, p2, deltabase, cs, flags, protocol_flags = header
211 return node, p1, p2, cs, deltabase, delta, flags, {}, protocol_flags
211 return node, p1, p2, cs, deltabase, delta, flags, {}, protocol_flags
212
212
213 def getchunks(self):
213 def getchunks(self):
214 """returns all the chunks contains in the bundle
214 """returns all the chunks contains in the bundle
215
215
216 Used when you need to forward the binary stream to a file or another
216 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
217 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.
218 block in case of sshrepo because it don't know the end of the stream.
219 """
219 """
220 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
220 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
221 # and a list of filelogs. For changegroup 3, we expect 4 parts:
221 # and a list of filelogs. For changegroup 3, we expect 4 parts:
222 # changelog, manifestlog, a list of tree manifestlogs, and a list of
222 # changelog, manifestlog, a list of tree manifestlogs, and a list of
223 # filelogs.
223 # filelogs.
224 #
224 #
225 # Changelog and manifestlog parts are terminated with empty chunks. The
225 # Changelog and manifestlog parts are terminated with empty chunks. The
226 # tree and file parts are a list of entry sections. Each entry section
226 # 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
227 # 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
228 # 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
229 # 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.
230 # chunk that was proceeded by no non-empty chunks.
231
231
232 parts = 0
232 parts = 0
233 while parts < 2 + self._grouplistcount:
233 while parts < 2 + self._grouplistcount:
234 noentries = True
234 noentries = True
235 while True:
235 while True:
236 chunk = getchunk(self)
236 chunk = getchunk(self)
237 if not chunk:
237 if not chunk:
238 # The first two empty chunks represent the end of the
238 # The first two empty chunks represent the end of the
239 # changelog and the manifestlog portions. The remaining
239 # changelog and the manifestlog portions. The remaining
240 # empty chunks represent either A) the end of individual
240 # empty chunks represent either A) the end of individual
241 # tree or file entries in the file list, or B) the end of
241 # 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
242 # the entire list. It's the end of the entire list if there
243 # were no entries (i.e. noentries is True).
243 # were no entries (i.e. noentries is True).
244 if parts < 2:
244 if parts < 2:
245 parts += 1
245 parts += 1
246 elif noentries:
246 elif noentries:
247 parts += 1
247 parts += 1
248 break
248 break
249 noentries = False
249 noentries = False
250 yield chunkheader(len(chunk))
250 yield chunkheader(len(chunk))
251 pos = 0
251 pos = 0
252 while pos < len(chunk):
252 while pos < len(chunk):
253 next = pos + 2 ** 20
253 next = pos + 2 ** 20
254 yield chunk[pos:next]
254 yield chunk[pos:next]
255 pos = next
255 pos = next
256 yield closechunk()
256 yield closechunk()
257
257
258 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
258 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
259 self.callback = prog.increment
259 self.callback = prog.increment
260 # no need to check for empty manifest group here:
260 # 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,
261 # 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
262 # no new manifest will be created and the manifest group will
263 # be empty during the pull
263 # be empty during the pull
264 self.manifestheader()
264 self.manifestheader()
265 deltas = self.deltaiter()
265 deltas = self.deltaiter()
266 storage = repo.manifestlog.getstorage(b'')
266 storage = repo.manifestlog.getstorage(b'')
267 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
267 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
268 prog.complete()
268 prog.complete()
269 self.callback = None
269 self.callback = None
270
270
271 def apply(
271 def apply(
272 self,
272 self,
273 repo,
273 repo,
274 tr,
274 tr,
275 srctype,
275 srctype,
276 url,
276 url,
277 targetphase=phases.draft,
277 targetphase=phases.draft,
278 expectedtotal=None,
278 expectedtotal=None,
279 sidedata_categories=None,
279 sidedata_categories=None,
280 ):
280 ):
281 """Add the changegroup returned by source.read() to this repo.
281 """Add the changegroup returned by source.read() to this repo.
282 srctype is a string like 'push', 'pull', or 'unbundle'. url is
282 srctype is a string like 'push', 'pull', or 'unbundle'. url is
283 the URL of the repo where this changegroup is coming from.
283 the URL of the repo where this changegroup is coming from.
284
284
285 Return an integer summarizing the change to this repo:
285 Return an integer summarizing the change to this repo:
286 - nothing changed or no source: 0
286 - nothing changed or no source: 0
287 - more heads than before: 1+added heads (2..n)
287 - more heads than before: 1+added heads (2..n)
288 - fewer heads than before: -1-removed heads (-2..-n)
288 - fewer heads than before: -1-removed heads (-2..-n)
289 - number of heads stays the same: 1
289 - number of heads stays the same: 1
290
290
291 `sidedata_categories` is an optional set of the remote's sidedata wanted
291 `sidedata_categories` is an optional set of the remote's sidedata wanted
292 categories.
292 categories.
293 """
293 """
294 repo = repo.unfiltered()
294 repo = repo.unfiltered()
295
295
296 # Only useful if we're adding sidedata categories. If both peers have
296 # Only useful if we're adding sidedata categories. If both peers have
297 # the same categories, then we simply don't do anything.
297 # the same categories, then we simply don't do anything.
298 adding_sidedata = (
298 adding_sidedata = (
299 (
299 (
300 requirements.REVLOGV2_REQUIREMENT in repo.requirements
300 requirements.REVLOGV2_REQUIREMENT in repo.requirements
301 or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
301 or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
302 )
302 )
303 and self.version == b'04'
303 and self.version == b'04'
304 and srctype == b'pull'
304 and srctype == b'pull'
305 )
305 )
306 if adding_sidedata:
306 if adding_sidedata:
307 sidedata_helpers = sidedatamod.get_sidedata_helpers(
307 sidedata_helpers = sidedatamod.get_sidedata_helpers(
308 repo,
308 repo,
309 sidedata_categories or set(),
309 sidedata_categories or set(),
310 pull=True,
310 pull=True,
311 )
311 )
312 else:
312 else:
313 sidedata_helpers = None
313 sidedata_helpers = None
314
314
315 def csmap(x):
315 def csmap(x):
316 repo.ui.debug(b"add changeset %s\n" % short(x))
316 repo.ui.debug(b"add changeset %s\n" % short(x))
317 return len(cl)
317 return len(cl)
318
318
319 def revmap(x):
319 def revmap(x):
320 return cl.rev(x)
320 return cl.rev(x)
321
321
322 try:
322 try:
323 # The transaction may already carry source information. In this
323 # The transaction may already carry source information. In this
324 # case we use the top level data. We overwrite the argument
324 # case we use the top level data. We overwrite the argument
325 # because we need to use the top level value (if they exist)
325 # because we need to use the top level value (if they exist)
326 # in this function.
326 # in this function.
327 srctype = tr.hookargs.setdefault(b'source', srctype)
327 srctype = tr.hookargs.setdefault(b'source', srctype)
328 tr.hookargs.setdefault(b'url', url)
328 tr.hookargs.setdefault(b'url', url)
329 repo.hook(
329 repo.hook(
330 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
330 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
331 )
331 )
332
332
333 # write changelog data to temp files so concurrent readers
333 # write changelog data to temp files so concurrent readers
334 # will not see an inconsistent view
334 # will not see an inconsistent view
335 cl = repo.changelog
335 cl = repo.changelog
336 cl.delayupdate(tr)
336 cl.delayupdate(tr)
337 oldheads = set(cl.heads())
337 oldheads = set(cl.heads())
338
338
339 trp = weakref.proxy(tr)
339 trp = weakref.proxy(tr)
340 # pull off the changeset group
340 # pull off the changeset group
341 repo.ui.status(_(b"adding changesets\n"))
341 repo.ui.status(_(b"adding changesets\n"))
342 clstart = len(cl)
342 clstart = len(cl)
343 progress = repo.ui.makeprogress(
343 progress = repo.ui.makeprogress(
344 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
344 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
345 )
345 )
346 self.callback = progress.increment
346 self.callback = progress.increment
347
347
348 efilesset = set()
348 efilesset = set()
349 duprevs = []
349 duprevs = []
350
350
351 def ondupchangelog(cl, rev):
351 def ondupchangelog(cl, rev):
352 if rev < clstart:
352 if rev < clstart:
353 duprevs.append(rev)
353 duprevs.append(rev) # pytype: disable=attribute-error
354
354
355 def onchangelog(cl, rev):
355 def onchangelog(cl, rev):
356 ctx = cl.changelogrevision(rev)
356 ctx = cl.changelogrevision(rev)
357 assert efilesset is not None # help pytype
357 efilesset.update(ctx.files)
358 efilesset.update(ctx.files)
358 repo.register_changeset(rev, ctx)
359 repo.register_changeset(rev, ctx)
359
360
360 self.changelogheader()
361 self.changelogheader()
361 deltas = self.deltaiter()
362 deltas = self.deltaiter()
362 if not cl.addgroup(
363 if not cl.addgroup(
363 deltas,
364 deltas,
364 csmap,
365 csmap,
365 trp,
366 trp,
366 alwayscache=True,
367 alwayscache=True,
367 addrevisioncb=onchangelog,
368 addrevisioncb=onchangelog,
368 duplicaterevisioncb=ondupchangelog,
369 duplicaterevisioncb=ondupchangelog,
369 ):
370 ):
370 repo.ui.develwarn(
371 repo.ui.develwarn(
371 b'applied empty changelog from changegroup',
372 b'applied empty changelog from changegroup',
372 config=b'warn-empty-changegroup',
373 config=b'warn-empty-changegroup',
373 )
374 )
374 efiles = len(efilesset)
375 efiles = len(efilesset)
375 clend = len(cl)
376 clend = len(cl)
376 changesets = clend - clstart
377 changesets = clend - clstart
377 progress.complete()
378 progress.complete()
378 del deltas
379 del deltas
379 # TODO Python 2.7 removal
380 # TODO Python 2.7 removal
380 # del efilesset
381 # del efilesset
381 efilesset = None
382 efilesset = None
382 self.callback = None
383 self.callback = None
383
384
384 # Keep track of the (non-changelog) revlogs we've updated and their
385 # Keep track of the (non-changelog) revlogs we've updated and their
385 # range of new revisions for sidedata rewrite.
386 # range of new revisions for sidedata rewrite.
386 # TODO do something more efficient than keeping the reference to
387 # TODO do something more efficient than keeping the reference to
387 # the revlogs, especially memory-wise.
388 # the revlogs, especially memory-wise.
388 touched_manifests = {}
389 touched_manifests = {}
389 touched_filelogs = {}
390 touched_filelogs = {}
390
391
391 # pull off the manifest group
392 # pull off the manifest group
392 repo.ui.status(_(b"adding manifests\n"))
393 repo.ui.status(_(b"adding manifests\n"))
393 # We know that we'll never have more manifests than we had
394 # We know that we'll never have more manifests than we had
394 # changesets.
395 # changesets.
395 progress = repo.ui.makeprogress(
396 progress = repo.ui.makeprogress(
396 _(b'manifests'), unit=_(b'chunks'), total=changesets
397 _(b'manifests'), unit=_(b'chunks'), total=changesets
397 )
398 )
398 on_manifest_rev = None
399 on_manifest_rev = None
399 if sidedata_helpers:
400 if sidedata_helpers:
400 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
401 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
401
402
402 def on_manifest_rev(manifest, rev):
403 def on_manifest_rev(manifest, rev):
403 range = touched_manifests.get(manifest)
404 range = touched_manifests.get(manifest)
404 if not range:
405 if not range:
405 touched_manifests[manifest] = (rev, rev)
406 touched_manifests[manifest] = (rev, rev)
406 else:
407 else:
407 assert rev == range[1] + 1
408 assert rev == range[1] + 1
408 touched_manifests[manifest] = (range[0], rev)
409 touched_manifests[manifest] = (range[0], rev)
409
410
410 self._unpackmanifests(
411 self._unpackmanifests(
411 repo,
412 repo,
412 revmap,
413 revmap,
413 trp,
414 trp,
414 progress,
415 progress,
415 addrevisioncb=on_manifest_rev,
416 addrevisioncb=on_manifest_rev,
416 )
417 )
417
418
418 needfiles = {}
419 needfiles = {}
419 if repo.ui.configbool(b'server', b'validate'):
420 if repo.ui.configbool(b'server', b'validate'):
420 cl = repo.changelog
421 cl = repo.changelog
421 ml = repo.manifestlog
422 ml = repo.manifestlog
422 # validate incoming csets have their manifests
423 # validate incoming csets have their manifests
423 for cset in pycompat.xrange(clstart, clend):
424 for cset in pycompat.xrange(clstart, clend):
424 mfnode = cl.changelogrevision(cset).manifest
425 mfnode = cl.changelogrevision(cset).manifest
425 mfest = ml[mfnode].readdelta()
426 mfest = ml[mfnode].readdelta()
426 # store file nodes we must see
427 # store file nodes we must see
427 for f, n in pycompat.iteritems(mfest):
428 for f, n in pycompat.iteritems(mfest):
428 needfiles.setdefault(f, set()).add(n)
429 needfiles.setdefault(f, set()).add(n)
429
430
430 on_filelog_rev = None
431 on_filelog_rev = None
431 if sidedata_helpers:
432 if sidedata_helpers:
432 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
433 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
433
434
434 def on_filelog_rev(filelog, rev):
435 def on_filelog_rev(filelog, rev):
435 range = touched_filelogs.get(filelog)
436 range = touched_filelogs.get(filelog)
436 if not range:
437 if not range:
437 touched_filelogs[filelog] = (rev, rev)
438 touched_filelogs[filelog] = (rev, rev)
438 else:
439 else:
439 assert rev == range[1] + 1
440 assert rev == range[1] + 1
440 touched_filelogs[filelog] = (range[0], rev)
441 touched_filelogs[filelog] = (range[0], rev)
441
442
442 # process the files
443 # process the files
443 repo.ui.status(_(b"adding file changes\n"))
444 repo.ui.status(_(b"adding file changes\n"))
444 newrevs, newfiles = _addchangegroupfiles(
445 newrevs, newfiles = _addchangegroupfiles(
445 repo,
446 repo,
446 self,
447 self,
447 revmap,
448 revmap,
448 trp,
449 trp,
449 efiles,
450 efiles,
450 needfiles,
451 needfiles,
451 addrevisioncb=on_filelog_rev,
452 addrevisioncb=on_filelog_rev,
452 )
453 )
453
454
454 if sidedata_helpers:
455 if sidedata_helpers:
455 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
456 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
456 cl.rewrite_sidedata(
457 cl.rewrite_sidedata(
457 trp, sidedata_helpers, clstart, clend - 1
458 trp, sidedata_helpers, clstart, clend - 1
458 )
459 )
459 for mf, (startrev, endrev) in touched_manifests.items():
460 for mf, (startrev, endrev) in touched_manifests.items():
460 mf.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
461 mf.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
461 for fl, (startrev, endrev) in touched_filelogs.items():
462 for fl, (startrev, endrev) in touched_filelogs.items():
462 fl.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
463 fl.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
463
464
464 # making sure the value exists
465 # making sure the value exists
465 tr.changes.setdefault(b'changegroup-count-changesets', 0)
466 tr.changes.setdefault(b'changegroup-count-changesets', 0)
466 tr.changes.setdefault(b'changegroup-count-revisions', 0)
467 tr.changes.setdefault(b'changegroup-count-revisions', 0)
467 tr.changes.setdefault(b'changegroup-count-files', 0)
468 tr.changes.setdefault(b'changegroup-count-files', 0)
468 tr.changes.setdefault(b'changegroup-count-heads', 0)
469 tr.changes.setdefault(b'changegroup-count-heads', 0)
469
470
470 # some code use bundle operation for internal purpose. They usually
471 # some code use bundle operation for internal purpose. They usually
471 # set `ui.quiet` to do this outside of user sight. Size the report
472 # set `ui.quiet` to do this outside of user sight. Size the report
472 # of such operation now happens at the end of the transaction, that
473 # of such operation now happens at the end of the transaction, that
473 # ui.quiet has not direct effect on the output.
474 # ui.quiet has not direct effect on the output.
474 #
475 #
475 # To preserve this intend use an inelegant hack, we fail to report
476 # To preserve this intend use an inelegant hack, we fail to report
476 # the change if `quiet` is set. We should probably move to
477 # the change if `quiet` is set. We should probably move to
477 # something better, but this is a good first step to allow the "end
478 # something better, but this is a good first step to allow the "end
478 # of transaction report" to pass tests.
479 # of transaction report" to pass tests.
479 if not repo.ui.quiet:
480 if not repo.ui.quiet:
480 tr.changes[b'changegroup-count-changesets'] += changesets
481 tr.changes[b'changegroup-count-changesets'] += changesets
481 tr.changes[b'changegroup-count-revisions'] += newrevs
482 tr.changes[b'changegroup-count-revisions'] += newrevs
482 tr.changes[b'changegroup-count-files'] += newfiles
483 tr.changes[b'changegroup-count-files'] += newfiles
483
484
484 deltaheads = 0
485 deltaheads = 0
485 if oldheads:
486 if oldheads:
486 heads = cl.heads()
487 heads = cl.heads()
487 deltaheads += len(heads) - len(oldheads)
488 deltaheads += len(heads) - len(oldheads)
488 for h in heads:
489 for h in heads:
489 if h not in oldheads and repo[h].closesbranch():
490 if h not in oldheads and repo[h].closesbranch():
490 deltaheads -= 1
491 deltaheads -= 1
491
492
492 # see previous comment about checking ui.quiet
493 # see previous comment about checking ui.quiet
493 if not repo.ui.quiet:
494 if not repo.ui.quiet:
494 tr.changes[b'changegroup-count-heads'] += deltaheads
495 tr.changes[b'changegroup-count-heads'] += deltaheads
495 repo.invalidatevolatilesets()
496 repo.invalidatevolatilesets()
496
497
497 if changesets > 0:
498 if changesets > 0:
498 if b'node' not in tr.hookargs:
499 if b'node' not in tr.hookargs:
499 tr.hookargs[b'node'] = hex(cl.node(clstart))
500 tr.hookargs[b'node'] = hex(cl.node(clstart))
500 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
501 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
501 hookargs = dict(tr.hookargs)
502 hookargs = dict(tr.hookargs)
502 else:
503 else:
503 hookargs = dict(tr.hookargs)
504 hookargs = dict(tr.hookargs)
504 hookargs[b'node'] = hex(cl.node(clstart))
505 hookargs[b'node'] = hex(cl.node(clstart))
505 hookargs[b'node_last'] = hex(cl.node(clend - 1))
506 hookargs[b'node_last'] = hex(cl.node(clend - 1))
506 repo.hook(
507 repo.hook(
507 b'pretxnchangegroup',
508 b'pretxnchangegroup',
508 throw=True,
509 throw=True,
509 **pycompat.strkwargs(hookargs)
510 **pycompat.strkwargs(hookargs)
510 )
511 )
511
512
512 added = pycompat.xrange(clstart, clend)
513 added = pycompat.xrange(clstart, clend)
513 phaseall = None
514 phaseall = None
514 if srctype in (b'push', b'serve'):
515 if srctype in (b'push', b'serve'):
515 # Old servers can not push the boundary themselves.
516 # Old servers can not push the boundary themselves.
516 # New servers won't push the boundary if changeset already
517 # New servers won't push the boundary if changeset already
517 # exists locally as secret
518 # exists locally as secret
518 #
519 #
519 # We should not use added here but the list of all change in
520 # We should not use added here but the list of all change in
520 # the bundle
521 # the bundle
521 if repo.publishing():
522 if repo.publishing():
522 targetphase = phaseall = phases.public
523 targetphase = phaseall = phases.public
523 else:
524 else:
524 # closer target phase computation
525 # closer target phase computation
525
526
526 # Those changesets have been pushed from the
527 # Those changesets have been pushed from the
527 # outside, their phases are going to be pushed
528 # outside, their phases are going to be pushed
528 # alongside. Therefor `targetphase` is
529 # alongside. Therefor `targetphase` is
529 # ignored.
530 # ignored.
530 targetphase = phaseall = phases.draft
531 targetphase = phaseall = phases.draft
531 if added:
532 if added:
532 phases.registernew(repo, tr, targetphase, added)
533 phases.registernew(repo, tr, targetphase, added)
533 if phaseall is not None:
534 if phaseall is not None:
534 if duprevs:
535 if duprevs:
535 duprevs.extend(added)
536 duprevs.extend(added)
536 else:
537 else:
537 duprevs = added
538 duprevs = added
538 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
539 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
539 duprevs = []
540 duprevs = []
540
541
541 if changesets > 0:
542 if changesets > 0:
542
543
543 def runhooks(unused_success):
544 def runhooks(unused_success):
544 # These hooks run when the lock releases, not when the
545 # These hooks run when the lock releases, not when the
545 # transaction closes. So it's possible for the changelog
546 # transaction closes. So it's possible for the changelog
546 # to have changed since we last saw it.
547 # to have changed since we last saw it.
547 if clstart >= len(repo):
548 if clstart >= len(repo):
548 return
549 return
549
550
550 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
551 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
551
552
552 for rev in added:
553 for rev in added:
553 args = hookargs.copy()
554 args = hookargs.copy()
554 args[b'node'] = hex(cl.node(rev))
555 args[b'node'] = hex(cl.node(rev))
555 del args[b'node_last']
556 del args[b'node_last']
556 repo.hook(b"incoming", **pycompat.strkwargs(args))
557 repo.hook(b"incoming", **pycompat.strkwargs(args))
557
558
558 newheads = [h for h in repo.heads() if h not in oldheads]
559 newheads = [h for h in repo.heads() if h not in oldheads]
559 repo.ui.log(
560 repo.ui.log(
560 b"incoming",
561 b"incoming",
561 b"%d incoming changes - new heads: %s\n",
562 b"%d incoming changes - new heads: %s\n",
562 len(added),
563 len(added),
563 b', '.join([hex(c[:6]) for c in newheads]),
564 b', '.join([hex(c[:6]) for c in newheads]),
564 )
565 )
565
566
566 tr.addpostclose(
567 tr.addpostclose(
567 b'changegroup-runhooks-%020i' % clstart,
568 b'changegroup-runhooks-%020i' % clstart,
568 lambda tr: repo._afterlock(runhooks),
569 lambda tr: repo._afterlock(runhooks),
569 )
570 )
570 finally:
571 finally:
571 repo.ui.flush()
572 repo.ui.flush()
572 # never return 0 here:
573 # never return 0 here:
573 if deltaheads < 0:
574 if deltaheads < 0:
574 ret = deltaheads - 1
575 ret = deltaheads - 1
575 else:
576 else:
576 ret = deltaheads + 1
577 ret = deltaheads + 1
577 return ret
578 return ret
578
579
579 def deltaiter(self):
580 def deltaiter(self):
580 """
581 """
581 returns an iterator of the deltas in this changegroup
582 returns an iterator of the deltas in this changegroup
582
583
583 Useful for passing to the underlying storage system to be stored.
584 Useful for passing to the underlying storage system to be stored.
584 """
585 """
585 chain = None
586 chain = None
586 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
587 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
587 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
588 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
588 yield chunkdata[:8]
589 yield chunkdata[:8]
589 chain = chunkdata[0]
590 chain = chunkdata[0]
590
591
591
592
592 class cg2unpacker(cg1unpacker):
593 class cg2unpacker(cg1unpacker):
593 """Unpacker for cg2 streams.
594 """Unpacker for cg2 streams.
594
595
595 cg2 streams add support for generaldelta, so the delta header
596 cg2 streams add support for generaldelta, so the delta header
596 format is slightly different. All other features about the data
597 format is slightly different. All other features about the data
597 remain the same.
598 remain the same.
598 """
599 """
599
600
600 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
601 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
601 deltaheadersize = deltaheader.size
602 deltaheadersize = deltaheader.size
602 version = b'02'
603 version = b'02'
603
604
604 def _deltaheader(self, headertuple, prevnode):
605 def _deltaheader(self, headertuple, prevnode):
605 node, p1, p2, deltabase, cs = headertuple
606 node, p1, p2, deltabase, cs = headertuple
606 flags = 0
607 flags = 0
607 protocol_flags = 0
608 protocol_flags = 0
608 return node, p1, p2, deltabase, cs, flags, protocol_flags
609 return node, p1, p2, deltabase, cs, flags, protocol_flags
609
610
610
611
611 class cg3unpacker(cg2unpacker):
612 class cg3unpacker(cg2unpacker):
612 """Unpacker for cg3 streams.
613 """Unpacker for cg3 streams.
613
614
614 cg3 streams add support for exchanging treemanifests and revlog
615 cg3 streams add support for exchanging treemanifests and revlog
615 flags. It adds the revlog flags to the delta header and an empty chunk
616 flags. It adds the revlog flags to the delta header and an empty chunk
616 separating manifests and files.
617 separating manifests and files.
617 """
618 """
618
619
619 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
620 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
620 deltaheadersize = deltaheader.size
621 deltaheadersize = deltaheader.size
621 version = b'03'
622 version = b'03'
622 _grouplistcount = 2 # One list of manifests and one list of files
623 _grouplistcount = 2 # One list of manifests and one list of files
623
624
624 def _deltaheader(self, headertuple, prevnode):
625 def _deltaheader(self, headertuple, prevnode):
625 node, p1, p2, deltabase, cs, flags = headertuple
626 node, p1, p2, deltabase, cs, flags = headertuple
626 protocol_flags = 0
627 protocol_flags = 0
627 return node, p1, p2, deltabase, cs, flags, protocol_flags
628 return node, p1, p2, deltabase, cs, flags, protocol_flags
628
629
629 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
630 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
630 super(cg3unpacker, self)._unpackmanifests(
631 super(cg3unpacker, self)._unpackmanifests(
631 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
632 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
632 )
633 )
633 for chunkdata in iter(self.filelogheader, {}):
634 for chunkdata in iter(self.filelogheader, {}):
634 # If we get here, there are directory manifests in the changegroup
635 # If we get here, there are directory manifests in the changegroup
635 d = chunkdata[b"filename"]
636 d = chunkdata[b"filename"]
636 repo.ui.debug(b"adding %s revisions\n" % d)
637 repo.ui.debug(b"adding %s revisions\n" % d)
637 deltas = self.deltaiter()
638 deltas = self.deltaiter()
638 if not repo.manifestlog.getstorage(d).addgroup(
639 if not repo.manifestlog.getstorage(d).addgroup(
639 deltas, revmap, trp, addrevisioncb=addrevisioncb
640 deltas, revmap, trp, addrevisioncb=addrevisioncb
640 ):
641 ):
641 raise error.Abort(_(b"received dir revlog group is empty"))
642 raise error.Abort(_(b"received dir revlog group is empty"))
642
643
643
644
644 class cg4unpacker(cg3unpacker):
645 class cg4unpacker(cg3unpacker):
645 """Unpacker for cg4 streams.
646 """Unpacker for cg4 streams.
646
647
647 cg4 streams add support for exchanging sidedata.
648 cg4 streams add support for exchanging sidedata.
648 """
649 """
649
650
650 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
651 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
651 deltaheadersize = deltaheader.size
652 deltaheadersize = deltaheader.size
652 version = b'04'
653 version = b'04'
653
654
654 def _deltaheader(self, headertuple, prevnode):
655 def _deltaheader(self, headertuple, prevnode):
655 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
656 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
656 return node, p1, p2, deltabase, cs, flags, protocol_flags
657 return node, p1, p2, deltabase, cs, flags, protocol_flags
657
658
658 def deltachunk(self, prevnode):
659 def deltachunk(self, prevnode):
659 res = super(cg4unpacker, self).deltachunk(prevnode)
660 res = super(cg4unpacker, self).deltachunk(prevnode)
660 if not res:
661 if not res:
661 return res
662 return res
662
663
663 (
664 (
664 node,
665 node,
665 p1,
666 p1,
666 p2,
667 p2,
667 cs,
668 cs,
668 deltabase,
669 deltabase,
669 delta,
670 delta,
670 flags,
671 flags,
671 sidedata,
672 sidedata,
672 protocol_flags,
673 protocol_flags,
673 ) = res
674 ) = res
674 assert not sidedata
675 assert not sidedata
675
676
676 sidedata = {}
677 sidedata = {}
677 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
678 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
678 sidedata_raw = getchunk(self._stream)
679 sidedata_raw = getchunk(self._stream)
679 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
680 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
680
681
681 return (
682 return (
682 node,
683 node,
683 p1,
684 p1,
684 p2,
685 p2,
685 cs,
686 cs,
686 deltabase,
687 deltabase,
687 delta,
688 delta,
688 flags,
689 flags,
689 sidedata,
690 sidedata,
690 protocol_flags,
691 protocol_flags,
691 )
692 )
692
693
693
694
694 class headerlessfixup(object):
695 class headerlessfixup(object):
695 def __init__(self, fh, h):
696 def __init__(self, fh, h):
696 self._h = h
697 self._h = h
697 self._fh = fh
698 self._fh = fh
698
699
699 def read(self, n):
700 def read(self, n):
700 if self._h:
701 if self._h:
701 d, self._h = self._h[:n], self._h[n:]
702 d, self._h = self._h[:n], self._h[n:]
702 if len(d) < n:
703 if len(d) < n:
703 d += readexactly(self._fh, n - len(d))
704 d += readexactly(self._fh, n - len(d))
704 return d
705 return d
705 return readexactly(self._fh, n)
706 return readexactly(self._fh, n)
706
707
707
708
708 def _revisiondeltatochunks(repo, delta, headerfn):
709 def _revisiondeltatochunks(repo, delta, headerfn):
709 """Serialize a revisiondelta to changegroup chunks."""
710 """Serialize a revisiondelta to changegroup chunks."""
710
711
711 # The captured revision delta may be encoded as a delta against
712 # The captured revision delta may be encoded as a delta against
712 # a base revision or as a full revision. The changegroup format
713 # a base revision or as a full revision. The changegroup format
713 # requires that everything on the wire be deltas. So for full
714 # requires that everything on the wire be deltas. So for full
714 # revisions, we need to invent a header that says to rewrite
715 # revisions, we need to invent a header that says to rewrite
715 # data.
716 # data.
716
717
717 if delta.delta is not None:
718 if delta.delta is not None:
718 prefix, data = b'', delta.delta
719 prefix, data = b'', delta.delta
719 elif delta.basenode == repo.nullid:
720 elif delta.basenode == repo.nullid:
720 data = delta.revision
721 data = delta.revision
721 prefix = mdiff.trivialdiffheader(len(data))
722 prefix = mdiff.trivialdiffheader(len(data))
722 else:
723 else:
723 data = delta.revision
724 data = delta.revision
724 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
725 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
725
726
726 meta = headerfn(delta)
727 meta = headerfn(delta)
727
728
728 yield chunkheader(len(meta) + len(prefix) + len(data))
729 yield chunkheader(len(meta) + len(prefix) + len(data))
729 yield meta
730 yield meta
730 if prefix:
731 if prefix:
731 yield prefix
732 yield prefix
732 yield data
733 yield data
733
734
734 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
735 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
735 # Need a separate chunk for sidedata to be able to differentiate
736 # Need a separate chunk for sidedata to be able to differentiate
736 # "raw delta" length and sidedata length
737 # "raw delta" length and sidedata length
737 sidedata = delta.sidedata
738 sidedata = delta.sidedata
738 yield chunkheader(len(sidedata))
739 yield chunkheader(len(sidedata))
739 yield sidedata
740 yield sidedata
740
741
741
742
742 def _sortnodesellipsis(store, nodes, cl, lookup):
743 def _sortnodesellipsis(store, nodes, cl, lookup):
743 """Sort nodes for changegroup generation."""
744 """Sort nodes for changegroup generation."""
744 # Ellipses serving mode.
745 # Ellipses serving mode.
745 #
746 #
746 # In a perfect world, we'd generate better ellipsis-ified graphs
747 # In a perfect world, we'd generate better ellipsis-ified graphs
747 # for non-changelog revlogs. In practice, we haven't started doing
748 # for non-changelog revlogs. In practice, we haven't started doing
748 # that yet, so the resulting DAGs for the manifestlog and filelogs
749 # that yet, so the resulting DAGs for the manifestlog and filelogs
749 # are actually full of bogus parentage on all the ellipsis
750 # are actually full of bogus parentage on all the ellipsis
750 # nodes. This has the side effect that, while the contents are
751 # nodes. This has the side effect that, while the contents are
751 # correct, the individual DAGs might be completely out of whack in
752 # correct, the individual DAGs might be completely out of whack in
752 # a case like 882681bc3166 and its ancestors (back about 10
753 # a case like 882681bc3166 and its ancestors (back about 10
753 # revisions or so) in the main hg repo.
754 # revisions or so) in the main hg repo.
754 #
755 #
755 # The one invariant we *know* holds is that the new (potentially
756 # The one invariant we *know* holds is that the new (potentially
756 # bogus) DAG shape will be valid if we order the nodes in the
757 # bogus) DAG shape will be valid if we order the nodes in the
757 # order that they're introduced in dramatis personae by the
758 # order that they're introduced in dramatis personae by the
758 # changelog, so what we do is we sort the non-changelog histories
759 # changelog, so what we do is we sort the non-changelog histories
759 # by the order in which they are used by the changelog.
760 # by the order in which they are used by the changelog.
760 key = lambda n: cl.rev(lookup(n))
761 key = lambda n: cl.rev(lookup(n))
761 return sorted(nodes, key=key)
762 return sorted(nodes, key=key)
762
763
763
764
764 def _resolvenarrowrevisioninfo(
765 def _resolvenarrowrevisioninfo(
765 cl,
766 cl,
766 store,
767 store,
767 ischangelog,
768 ischangelog,
768 rev,
769 rev,
769 linkrev,
770 linkrev,
770 linknode,
771 linknode,
771 clrevtolocalrev,
772 clrevtolocalrev,
772 fullclnodes,
773 fullclnodes,
773 precomputedellipsis,
774 precomputedellipsis,
774 ):
775 ):
775 linkparents = precomputedellipsis[linkrev]
776 linkparents = precomputedellipsis[linkrev]
776
777
777 def local(clrev):
778 def local(clrev):
778 """Turn a changelog revnum into a local revnum.
779 """Turn a changelog revnum into a local revnum.
779
780
780 The ellipsis dag is stored as revnums on the changelog,
781 The ellipsis dag is stored as revnums on the changelog,
781 but when we're producing ellipsis entries for
782 but when we're producing ellipsis entries for
782 non-changelog revlogs, we need to turn those numbers into
783 non-changelog revlogs, we need to turn those numbers into
783 something local. This does that for us, and during the
784 something local. This does that for us, and during the
784 changelog sending phase will also expand the stored
785 changelog sending phase will also expand the stored
785 mappings as needed.
786 mappings as needed.
786 """
787 """
787 if clrev == nullrev:
788 if clrev == nullrev:
788 return nullrev
789 return nullrev
789
790
790 if ischangelog:
791 if ischangelog:
791 return clrev
792 return clrev
792
793
793 # Walk the ellipsis-ized changelog breadth-first looking for a
794 # Walk the ellipsis-ized changelog breadth-first looking for a
794 # change that has been linked from the current revlog.
795 # change that has been linked from the current revlog.
795 #
796 #
796 # For a flat manifest revlog only a single step should be necessary
797 # For a flat manifest revlog only a single step should be necessary
797 # as all relevant changelog entries are relevant to the flat
798 # as all relevant changelog entries are relevant to the flat
798 # manifest.
799 # manifest.
799 #
800 #
800 # For a filelog or tree manifest dirlog however not every changelog
801 # For a filelog or tree manifest dirlog however not every changelog
801 # entry will have been relevant, so we need to skip some changelog
802 # entry will have been relevant, so we need to skip some changelog
802 # nodes even after ellipsis-izing.
803 # nodes even after ellipsis-izing.
803 walk = [clrev]
804 walk = [clrev]
804 while walk:
805 while walk:
805 p = walk[0]
806 p = walk[0]
806 walk = walk[1:]
807 walk = walk[1:]
807 if p in clrevtolocalrev:
808 if p in clrevtolocalrev:
808 return clrevtolocalrev[p]
809 return clrevtolocalrev[p]
809 elif p in fullclnodes:
810 elif p in fullclnodes:
810 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
811 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
811 elif p in precomputedellipsis:
812 elif p in precomputedellipsis:
812 walk.extend(
813 walk.extend(
813 [pp for pp in precomputedellipsis[p] if pp != nullrev]
814 [pp for pp in precomputedellipsis[p] if pp != nullrev]
814 )
815 )
815 else:
816 else:
816 # In this case, we've got an ellipsis with parents
817 # In this case, we've got an ellipsis with parents
817 # outside the current bundle (likely an
818 # outside the current bundle (likely an
818 # incremental pull). We "know" that we can use the
819 # incremental pull). We "know" that we can use the
819 # value of this same revlog at whatever revision
820 # value of this same revlog at whatever revision
820 # is pointed to by linknode. "Know" is in scare
821 # is pointed to by linknode. "Know" is in scare
821 # quotes because I haven't done enough examination
822 # quotes because I haven't done enough examination
822 # of edge cases to convince myself this is really
823 # of edge cases to convince myself this is really
823 # a fact - it works for all the (admittedly
824 # a fact - it works for all the (admittedly
824 # thorough) cases in our testsuite, but I would be
825 # thorough) cases in our testsuite, but I would be
825 # somewhat unsurprised to find a case in the wild
826 # somewhat unsurprised to find a case in the wild
826 # where this breaks down a bit. That said, I don't
827 # where this breaks down a bit. That said, I don't
827 # know if it would hurt anything.
828 # know if it would hurt anything.
828 for i in pycompat.xrange(rev, 0, -1):
829 for i in pycompat.xrange(rev, 0, -1):
829 if store.linkrev(i) == clrev:
830 if store.linkrev(i) == clrev:
830 return i
831 return i
831 # We failed to resolve a parent for this node, so
832 # We failed to resolve a parent for this node, so
832 # we crash the changegroup construction.
833 # we crash the changegroup construction.
833 if util.safehasattr(store, 'target'):
834 if util.safehasattr(store, 'target'):
834 target = store.display_id
835 target = store.display_id
835 else:
836 else:
836 # some revlog not actually a revlog
837 # some revlog not actually a revlog
837 target = store._revlog.display_id
838 target = store._revlog.display_id
838
839
839 raise error.Abort(
840 raise error.Abort(
840 b"unable to resolve parent while packing '%s' %r"
841 b"unable to resolve parent while packing '%s' %r"
841 b' for changeset %r' % (target, rev, clrev)
842 b' for changeset %r' % (target, rev, clrev)
842 )
843 )
843
844
844 return nullrev
845 return nullrev
845
846
846 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
847 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
847 p1, p2 = nullrev, nullrev
848 p1, p2 = nullrev, nullrev
848 elif len(linkparents) == 1:
849 elif len(linkparents) == 1:
849 (p1,) = sorted(local(p) for p in linkparents)
850 (p1,) = sorted(local(p) for p in linkparents)
850 p2 = nullrev
851 p2 = nullrev
851 else:
852 else:
852 p1, p2 = sorted(local(p) for p in linkparents)
853 p1, p2 = sorted(local(p) for p in linkparents)
853
854
854 p1node, p2node = store.node(p1), store.node(p2)
855 p1node, p2node = store.node(p1), store.node(p2)
855
856
856 return p1node, p2node, linknode
857 return p1node, p2node, linknode
857
858
858
859
859 def deltagroup(
860 def deltagroup(
860 repo,
861 repo,
861 store,
862 store,
862 nodes,
863 nodes,
863 ischangelog,
864 ischangelog,
864 lookup,
865 lookup,
865 forcedeltaparentprev,
866 forcedeltaparentprev,
866 topic=None,
867 topic=None,
867 ellipses=False,
868 ellipses=False,
868 clrevtolocalrev=None,
869 clrevtolocalrev=None,
869 fullclnodes=None,
870 fullclnodes=None,
870 precomputedellipsis=None,
871 precomputedellipsis=None,
871 sidedata_helpers=None,
872 sidedata_helpers=None,
872 ):
873 ):
873 """Calculate deltas for a set of revisions.
874 """Calculate deltas for a set of revisions.
874
875
875 Is a generator of ``revisiondelta`` instances.
876 Is a generator of ``revisiondelta`` instances.
876
877
877 If topic is not None, progress detail will be generated using this
878 If topic is not None, progress detail will be generated using this
878 topic name (e.g. changesets, manifests, etc).
879 topic name (e.g. changesets, manifests, etc).
879
880
880 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
881 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
881 `sidedata_helpers`.
882 `sidedata_helpers`.
882 """
883 """
883 if not nodes:
884 if not nodes:
884 return
885 return
885
886
886 cl = repo.changelog
887 cl = repo.changelog
887
888
888 if ischangelog:
889 if ischangelog:
889 # `hg log` shows changesets in storage order. To preserve order
890 # `hg log` shows changesets in storage order. To preserve order
890 # across clones, send out changesets in storage order.
891 # across clones, send out changesets in storage order.
891 nodesorder = b'storage'
892 nodesorder = b'storage'
892 elif ellipses:
893 elif ellipses:
893 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
894 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
894 nodesorder = b'nodes'
895 nodesorder = b'nodes'
895 else:
896 else:
896 nodesorder = None
897 nodesorder = None
897
898
898 # Perform ellipses filtering and revision massaging. We do this before
899 # Perform ellipses filtering and revision massaging. We do this before
899 # emitrevisions() because a) filtering out revisions creates less work
900 # emitrevisions() because a) filtering out revisions creates less work
900 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
901 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
901 # assumptions about delta choices and we would possibly send a delta
902 # assumptions about delta choices and we would possibly send a delta
902 # referencing a missing base revision.
903 # referencing a missing base revision.
903 #
904 #
904 # Also, calling lookup() has side-effects with regards to populating
905 # Also, calling lookup() has side-effects with regards to populating
905 # data structures. If we don't call lookup() for each node or if we call
906 # data structures. If we don't call lookup() for each node or if we call
906 # lookup() after the first pass through each node, things can break -
907 # lookup() after the first pass through each node, things can break -
907 # possibly intermittently depending on the python hash seed! For that
908 # possibly intermittently depending on the python hash seed! For that
908 # reason, we store a mapping of all linknodes during the initial node
909 # reason, we store a mapping of all linknodes during the initial node
909 # pass rather than use lookup() on the output side.
910 # pass rather than use lookup() on the output side.
910 if ellipses:
911 if ellipses:
911 filtered = []
912 filtered = []
912 adjustedparents = {}
913 adjustedparents = {}
913 linknodes = {}
914 linknodes = {}
914
915
915 for node in nodes:
916 for node in nodes:
916 rev = store.rev(node)
917 rev = store.rev(node)
917 linknode = lookup(node)
918 linknode = lookup(node)
918 linkrev = cl.rev(linknode)
919 linkrev = cl.rev(linknode)
919 clrevtolocalrev[linkrev] = rev
920 clrevtolocalrev[linkrev] = rev
920
921
921 # If linknode is in fullclnodes, it means the corresponding
922 # If linknode is in fullclnodes, it means the corresponding
922 # changeset was a full changeset and is being sent unaltered.
923 # changeset was a full changeset and is being sent unaltered.
923 if linknode in fullclnodes:
924 if linknode in fullclnodes:
924 linknodes[node] = linknode
925 linknodes[node] = linknode
925
926
926 # If the corresponding changeset wasn't in the set computed
927 # If the corresponding changeset wasn't in the set computed
927 # as relevant to us, it should be dropped outright.
928 # as relevant to us, it should be dropped outright.
928 elif linkrev not in precomputedellipsis:
929 elif linkrev not in precomputedellipsis:
929 continue
930 continue
930
931
931 else:
932 else:
932 # We could probably do this later and avoid the dict
933 # We could probably do this later and avoid the dict
933 # holding state. But it likely doesn't matter.
934 # holding state. But it likely doesn't matter.
934 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
935 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
935 cl,
936 cl,
936 store,
937 store,
937 ischangelog,
938 ischangelog,
938 rev,
939 rev,
939 linkrev,
940 linkrev,
940 linknode,
941 linknode,
941 clrevtolocalrev,
942 clrevtolocalrev,
942 fullclnodes,
943 fullclnodes,
943 precomputedellipsis,
944 precomputedellipsis,
944 )
945 )
945
946
946 adjustedparents[node] = (p1node, p2node)
947 adjustedparents[node] = (p1node, p2node)
947 linknodes[node] = linknode
948 linknodes[node] = linknode
948
949
949 filtered.append(node)
950 filtered.append(node)
950
951
951 nodes = filtered
952 nodes = filtered
952
953
953 # We expect the first pass to be fast, so we only engage the progress
954 # We expect the first pass to be fast, so we only engage the progress
954 # meter for constructing the revision deltas.
955 # meter for constructing the revision deltas.
955 progress = None
956 progress = None
956 if topic is not None:
957 if topic is not None:
957 progress = repo.ui.makeprogress(
958 progress = repo.ui.makeprogress(
958 topic, unit=_(b'chunks'), total=len(nodes)
959 topic, unit=_(b'chunks'), total=len(nodes)
959 )
960 )
960
961
961 configtarget = repo.ui.config(b'devel', b'bundle.delta')
962 configtarget = repo.ui.config(b'devel', b'bundle.delta')
962 if configtarget not in (b'', b'p1', b'full'):
963 if configtarget not in (b'', b'p1', b'full'):
963 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
964 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
964 repo.ui.warn(msg % configtarget)
965 repo.ui.warn(msg % configtarget)
965
966
966 deltamode = repository.CG_DELTAMODE_STD
967 deltamode = repository.CG_DELTAMODE_STD
967 if forcedeltaparentprev:
968 if forcedeltaparentprev:
968 deltamode = repository.CG_DELTAMODE_PREV
969 deltamode = repository.CG_DELTAMODE_PREV
969 elif configtarget == b'p1':
970 elif configtarget == b'p1':
970 deltamode = repository.CG_DELTAMODE_P1
971 deltamode = repository.CG_DELTAMODE_P1
971 elif configtarget == b'full':
972 elif configtarget == b'full':
972 deltamode = repository.CG_DELTAMODE_FULL
973 deltamode = repository.CG_DELTAMODE_FULL
973
974
974 revisions = store.emitrevisions(
975 revisions = store.emitrevisions(
975 nodes,
976 nodes,
976 nodesorder=nodesorder,
977 nodesorder=nodesorder,
977 revisiondata=True,
978 revisiondata=True,
978 assumehaveparentrevisions=not ellipses,
979 assumehaveparentrevisions=not ellipses,
979 deltamode=deltamode,
980 deltamode=deltamode,
980 sidedata_helpers=sidedata_helpers,
981 sidedata_helpers=sidedata_helpers,
981 )
982 )
982
983
983 for i, revision in enumerate(revisions):
984 for i, revision in enumerate(revisions):
984 if progress:
985 if progress:
985 progress.update(i + 1)
986 progress.update(i + 1)
986
987
987 if ellipses:
988 if ellipses:
988 linknode = linknodes[revision.node]
989 linknode = linknodes[revision.node]
989
990
990 if revision.node in adjustedparents:
991 if revision.node in adjustedparents:
991 p1node, p2node = adjustedparents[revision.node]
992 p1node, p2node = adjustedparents[revision.node]
992 revision.p1node = p1node
993 revision.p1node = p1node
993 revision.p2node = p2node
994 revision.p2node = p2node
994 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
995 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
995
996
996 else:
997 else:
997 linknode = lookup(revision.node)
998 linknode = lookup(revision.node)
998
999
999 revision.linknode = linknode
1000 revision.linknode = linknode
1000 yield revision
1001 yield revision
1001
1002
1002 if progress:
1003 if progress:
1003 progress.complete()
1004 progress.complete()
1004
1005
1005
1006
1006 class cgpacker(object):
1007 class cgpacker(object):
1007 def __init__(
1008 def __init__(
1008 self,
1009 self,
1009 repo,
1010 repo,
1010 oldmatcher,
1011 oldmatcher,
1011 matcher,
1012 matcher,
1012 version,
1013 version,
1013 builddeltaheader,
1014 builddeltaheader,
1014 manifestsend,
1015 manifestsend,
1015 forcedeltaparentprev=False,
1016 forcedeltaparentprev=False,
1016 bundlecaps=None,
1017 bundlecaps=None,
1017 ellipses=False,
1018 ellipses=False,
1018 shallow=False,
1019 shallow=False,
1019 ellipsisroots=None,
1020 ellipsisroots=None,
1020 fullnodes=None,
1021 fullnodes=None,
1021 remote_sidedata=None,
1022 remote_sidedata=None,
1022 ):
1023 ):
1023 """Given a source repo, construct a bundler.
1024 """Given a source repo, construct a bundler.
1024
1025
1025 oldmatcher is a matcher that matches on files the client already has.
1026 oldmatcher is a matcher that matches on files the client already has.
1026 These will not be included in the changegroup.
1027 These will not be included in the changegroup.
1027
1028
1028 matcher is a matcher that matches on files to include in the
1029 matcher is a matcher that matches on files to include in the
1029 changegroup. Used to facilitate sparse changegroups.
1030 changegroup. Used to facilitate sparse changegroups.
1030
1031
1031 forcedeltaparentprev indicates whether delta parents must be against
1032 forcedeltaparentprev indicates whether delta parents must be against
1032 the previous revision in a delta group. This should only be used for
1033 the previous revision in a delta group. This should only be used for
1033 compatibility with changegroup version 1.
1034 compatibility with changegroup version 1.
1034
1035
1035 builddeltaheader is a callable that constructs the header for a group
1036 builddeltaheader is a callable that constructs the header for a group
1036 delta.
1037 delta.
1037
1038
1038 manifestsend is a chunk to send after manifests have been fully emitted.
1039 manifestsend is a chunk to send after manifests have been fully emitted.
1039
1040
1040 ellipses indicates whether ellipsis serving mode is enabled.
1041 ellipses indicates whether ellipsis serving mode is enabled.
1041
1042
1042 bundlecaps is optional and can be used to specify the set of
1043 bundlecaps is optional and can be used to specify the set of
1043 capabilities which can be used to build the bundle. While bundlecaps is
1044 capabilities which can be used to build the bundle. While bundlecaps is
1044 unused in core Mercurial, extensions rely on this feature to communicate
1045 unused in core Mercurial, extensions rely on this feature to communicate
1045 capabilities to customize the changegroup packer.
1046 capabilities to customize the changegroup packer.
1046
1047
1047 shallow indicates whether shallow data might be sent. The packer may
1048 shallow indicates whether shallow data might be sent. The packer may
1048 need to pack file contents not introduced by the changes being packed.
1049 need to pack file contents not introduced by the changes being packed.
1049
1050
1050 fullnodes is the set of changelog nodes which should not be ellipsis
1051 fullnodes is the set of changelog nodes which should not be ellipsis
1051 nodes. We store this rather than the set of nodes that should be
1052 nodes. We store this rather than the set of nodes that should be
1052 ellipsis because for very large histories we expect this to be
1053 ellipsis because for very large histories we expect this to be
1053 significantly smaller.
1054 significantly smaller.
1054
1055
1055 remote_sidedata is the set of sidedata categories wanted by the remote.
1056 remote_sidedata is the set of sidedata categories wanted by the remote.
1056 """
1057 """
1057 assert oldmatcher
1058 assert oldmatcher
1058 assert matcher
1059 assert matcher
1059 self._oldmatcher = oldmatcher
1060 self._oldmatcher = oldmatcher
1060 self._matcher = matcher
1061 self._matcher = matcher
1061
1062
1062 self.version = version
1063 self.version = version
1063 self._forcedeltaparentprev = forcedeltaparentprev
1064 self._forcedeltaparentprev = forcedeltaparentprev
1064 self._builddeltaheader = builddeltaheader
1065 self._builddeltaheader = builddeltaheader
1065 self._manifestsend = manifestsend
1066 self._manifestsend = manifestsend
1066 self._ellipses = ellipses
1067 self._ellipses = ellipses
1067
1068
1068 # Set of capabilities we can use to build the bundle.
1069 # Set of capabilities we can use to build the bundle.
1069 if bundlecaps is None:
1070 if bundlecaps is None:
1070 bundlecaps = set()
1071 bundlecaps = set()
1071 self._bundlecaps = bundlecaps
1072 self._bundlecaps = bundlecaps
1072 if remote_sidedata is None:
1073 if remote_sidedata is None:
1073 remote_sidedata = set()
1074 remote_sidedata = set()
1074 self._remote_sidedata = remote_sidedata
1075 self._remote_sidedata = remote_sidedata
1075 self._isshallow = shallow
1076 self._isshallow = shallow
1076 self._fullclnodes = fullnodes
1077 self._fullclnodes = fullnodes
1077
1078
1078 # Maps ellipsis revs to their roots at the changelog level.
1079 # Maps ellipsis revs to their roots at the changelog level.
1079 self._precomputedellipsis = ellipsisroots
1080 self._precomputedellipsis = ellipsisroots
1080
1081
1081 self._repo = repo
1082 self._repo = repo
1082
1083
1083 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1084 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1084 self._verbosenote = self._repo.ui.note
1085 self._verbosenote = self._repo.ui.note
1085 else:
1086 else:
1086 self._verbosenote = lambda s: None
1087 self._verbosenote = lambda s: None
1087
1088
1088 def generate(
1089 def generate(
1089 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1090 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1090 ):
1091 ):
1091 """Yield a sequence of changegroup byte chunks.
1092 """Yield a sequence of changegroup byte chunks.
1092 If changelog is False, changelog data won't be added to changegroup
1093 If changelog is False, changelog data won't be added to changegroup
1093 """
1094 """
1094
1095
1095 repo = self._repo
1096 repo = self._repo
1096 cl = repo.changelog
1097 cl = repo.changelog
1097
1098
1098 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1099 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1099 size = 0
1100 size = 0
1100
1101
1101 sidedata_helpers = None
1102 sidedata_helpers = None
1102 if self.version == b'04':
1103 if self.version == b'04':
1103 remote_sidedata = self._remote_sidedata
1104 remote_sidedata = self._remote_sidedata
1104 if source == b'strip':
1105 if source == b'strip':
1105 # We're our own remote when stripping, get the no-op helpers
1106 # We're our own remote when stripping, get the no-op helpers
1106 # TODO a better approach would be for the strip bundle to
1107 # TODO a better approach would be for the strip bundle to
1107 # correctly advertise its sidedata categories directly.
1108 # correctly advertise its sidedata categories directly.
1108 remote_sidedata = repo._wanted_sidedata
1109 remote_sidedata = repo._wanted_sidedata
1109 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1110 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1110 repo, remote_sidedata
1111 repo, remote_sidedata
1111 )
1112 )
1112
1113
1113 clstate, deltas = self._generatechangelog(
1114 clstate, deltas = self._generatechangelog(
1114 cl,
1115 cl,
1115 clnodes,
1116 clnodes,
1116 generate=changelog,
1117 generate=changelog,
1117 sidedata_helpers=sidedata_helpers,
1118 sidedata_helpers=sidedata_helpers,
1118 )
1119 )
1119 for delta in deltas:
1120 for delta in deltas:
1120 for chunk in _revisiondeltatochunks(
1121 for chunk in _revisiondeltatochunks(
1121 self._repo, delta, self._builddeltaheader
1122 self._repo, delta, self._builddeltaheader
1122 ):
1123 ):
1123 size += len(chunk)
1124 size += len(chunk)
1124 yield chunk
1125 yield chunk
1125
1126
1126 close = closechunk()
1127 close = closechunk()
1127 size += len(close)
1128 size += len(close)
1128 yield closechunk()
1129 yield closechunk()
1129
1130
1130 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1131 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1131
1132
1132 clrevorder = clstate[b'clrevorder']
1133 clrevorder = clstate[b'clrevorder']
1133 manifests = clstate[b'manifests']
1134 manifests = clstate[b'manifests']
1134 changedfiles = clstate[b'changedfiles']
1135 changedfiles = clstate[b'changedfiles']
1135
1136
1136 # We need to make sure that the linkrev in the changegroup refers to
1137 # We need to make sure that the linkrev in the changegroup refers to
1137 # the first changeset that introduced the manifest or file revision.
1138 # the first changeset that introduced the manifest or file revision.
1138 # The fastpath is usually safer than the slowpath, because the filelogs
1139 # The fastpath is usually safer than the slowpath, because the filelogs
1139 # are walked in revlog order.
1140 # are walked in revlog order.
1140 #
1141 #
1141 # When taking the slowpath when the manifest revlog uses generaldelta,
1142 # When taking the slowpath when the manifest revlog uses generaldelta,
1142 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1143 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1143 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1144 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1144 #
1145 #
1145 # When taking the fastpath, we are only vulnerable to reordering
1146 # When taking the fastpath, we are only vulnerable to reordering
1146 # of the changelog itself. The changelog never uses generaldelta and is
1147 # of the changelog itself. The changelog never uses generaldelta and is
1147 # never reordered. To handle this case, we simply take the slowpath,
1148 # never reordered. To handle this case, we simply take the slowpath,
1148 # which already has the 'clrevorder' logic. This was also fixed in
1149 # which already has the 'clrevorder' logic. This was also fixed in
1149 # cc0ff93d0c0c.
1150 # cc0ff93d0c0c.
1150
1151
1151 # Treemanifests don't work correctly with fastpathlinkrev
1152 # Treemanifests don't work correctly with fastpathlinkrev
1152 # either, because we don't discover which directory nodes to
1153 # either, because we don't discover which directory nodes to
1153 # send along with files. This could probably be fixed.
1154 # send along with files. This could probably be fixed.
1154 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1155 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1155
1156
1156 fnodes = {} # needed file nodes
1157 fnodes = {} # needed file nodes
1157
1158
1158 size = 0
1159 size = 0
1159 it = self.generatemanifests(
1160 it = self.generatemanifests(
1160 commonrevs,
1161 commonrevs,
1161 clrevorder,
1162 clrevorder,
1162 fastpathlinkrev,
1163 fastpathlinkrev,
1163 manifests,
1164 manifests,
1164 fnodes,
1165 fnodes,
1165 source,
1166 source,
1166 clstate[b'clrevtomanifestrev'],
1167 clstate[b'clrevtomanifestrev'],
1167 sidedata_helpers=sidedata_helpers,
1168 sidedata_helpers=sidedata_helpers,
1168 )
1169 )
1169
1170
1170 for tree, deltas in it:
1171 for tree, deltas in it:
1171 if tree:
1172 if tree:
1172 assert self.version in (b'03', b'04')
1173 assert self.version in (b'03', b'04')
1173 chunk = _fileheader(tree)
1174 chunk = _fileheader(tree)
1174 size += len(chunk)
1175 size += len(chunk)
1175 yield chunk
1176 yield chunk
1176
1177
1177 for delta in deltas:
1178 for delta in deltas:
1178 chunks = _revisiondeltatochunks(
1179 chunks = _revisiondeltatochunks(
1179 self._repo, delta, self._builddeltaheader
1180 self._repo, delta, self._builddeltaheader
1180 )
1181 )
1181 for chunk in chunks:
1182 for chunk in chunks:
1182 size += len(chunk)
1183 size += len(chunk)
1183 yield chunk
1184 yield chunk
1184
1185
1185 close = closechunk()
1186 close = closechunk()
1186 size += len(close)
1187 size += len(close)
1187 yield close
1188 yield close
1188
1189
1189 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1190 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1190 yield self._manifestsend
1191 yield self._manifestsend
1191
1192
1192 mfdicts = None
1193 mfdicts = None
1193 if self._ellipses and self._isshallow:
1194 if self._ellipses and self._isshallow:
1194 mfdicts = [
1195 mfdicts = [
1195 (repo.manifestlog[n].read(), lr)
1196 (repo.manifestlog[n].read(), lr)
1196 for (n, lr) in pycompat.iteritems(manifests)
1197 for (n, lr) in pycompat.iteritems(manifests)
1197 ]
1198 ]
1198
1199
1199 manifests.clear()
1200 manifests.clear()
1200 clrevs = {cl.rev(x) for x in clnodes}
1201 clrevs = {cl.rev(x) for x in clnodes}
1201
1202
1202 it = self.generatefiles(
1203 it = self.generatefiles(
1203 changedfiles,
1204 changedfiles,
1204 commonrevs,
1205 commonrevs,
1205 source,
1206 source,
1206 mfdicts,
1207 mfdicts,
1207 fastpathlinkrev,
1208 fastpathlinkrev,
1208 fnodes,
1209 fnodes,
1209 clrevs,
1210 clrevs,
1210 sidedata_helpers=sidedata_helpers,
1211 sidedata_helpers=sidedata_helpers,
1211 )
1212 )
1212
1213
1213 for path, deltas in it:
1214 for path, deltas in it:
1214 h = _fileheader(path)
1215 h = _fileheader(path)
1215 size = len(h)
1216 size = len(h)
1216 yield h
1217 yield h
1217
1218
1218 for delta in deltas:
1219 for delta in deltas:
1219 chunks = _revisiondeltatochunks(
1220 chunks = _revisiondeltatochunks(
1220 self._repo, delta, self._builddeltaheader
1221 self._repo, delta, self._builddeltaheader
1221 )
1222 )
1222 for chunk in chunks:
1223 for chunk in chunks:
1223 size += len(chunk)
1224 size += len(chunk)
1224 yield chunk
1225 yield chunk
1225
1226
1226 close = closechunk()
1227 close = closechunk()
1227 size += len(close)
1228 size += len(close)
1228 yield close
1229 yield close
1229
1230
1230 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1231 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1231
1232
1232 yield closechunk()
1233 yield closechunk()
1233
1234
1234 if clnodes:
1235 if clnodes:
1235 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1236 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1236
1237
1237 def _generatechangelog(
1238 def _generatechangelog(
1238 self, cl, nodes, generate=True, sidedata_helpers=None
1239 self, cl, nodes, generate=True, sidedata_helpers=None
1239 ):
1240 ):
1240 """Generate data for changelog chunks.
1241 """Generate data for changelog chunks.
1241
1242
1242 Returns a 2-tuple of a dict containing state and an iterable of
1243 Returns a 2-tuple of a dict containing state and an iterable of
1243 byte chunks. The state will not be fully populated until the
1244 byte chunks. The state will not be fully populated until the
1244 chunk stream has been fully consumed.
1245 chunk stream has been fully consumed.
1245
1246
1246 if generate is False, the state will be fully populated and no chunk
1247 if generate is False, the state will be fully populated and no chunk
1247 stream will be yielded
1248 stream will be yielded
1248
1249
1249 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1250 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1250 `sidedata_helpers`.
1251 `sidedata_helpers`.
1251 """
1252 """
1252 clrevorder = {}
1253 clrevorder = {}
1253 manifests = {}
1254 manifests = {}
1254 mfl = self._repo.manifestlog
1255 mfl = self._repo.manifestlog
1255 changedfiles = set()
1256 changedfiles = set()
1256 clrevtomanifestrev = {}
1257 clrevtomanifestrev = {}
1257
1258
1258 state = {
1259 state = {
1259 b'clrevorder': clrevorder,
1260 b'clrevorder': clrevorder,
1260 b'manifests': manifests,
1261 b'manifests': manifests,
1261 b'changedfiles': changedfiles,
1262 b'changedfiles': changedfiles,
1262 b'clrevtomanifestrev': clrevtomanifestrev,
1263 b'clrevtomanifestrev': clrevtomanifestrev,
1263 }
1264 }
1264
1265
1265 if not (generate or self._ellipses):
1266 if not (generate or self._ellipses):
1266 # sort the nodes in storage order
1267 # sort the nodes in storage order
1267 nodes = sorted(nodes, key=cl.rev)
1268 nodes = sorted(nodes, key=cl.rev)
1268 for node in nodes:
1269 for node in nodes:
1269 c = cl.changelogrevision(node)
1270 c = cl.changelogrevision(node)
1270 clrevorder[node] = len(clrevorder)
1271 clrevorder[node] = len(clrevorder)
1271 # record the first changeset introducing this manifest version
1272 # record the first changeset introducing this manifest version
1272 manifests.setdefault(c.manifest, node)
1273 manifests.setdefault(c.manifest, node)
1273 # Record a complete list of potentially-changed files in
1274 # Record a complete list of potentially-changed files in
1274 # this manifest.
1275 # this manifest.
1275 changedfiles.update(c.files)
1276 changedfiles.update(c.files)
1276
1277
1277 return state, ()
1278 return state, ()
1278
1279
1279 # Callback for the changelog, used to collect changed files and
1280 # Callback for the changelog, used to collect changed files and
1280 # manifest nodes.
1281 # manifest nodes.
1281 # Returns the linkrev node (identity in the changelog case).
1282 # Returns the linkrev node (identity in the changelog case).
1282 def lookupcl(x):
1283 def lookupcl(x):
1283 c = cl.changelogrevision(x)
1284 c = cl.changelogrevision(x)
1284 clrevorder[x] = len(clrevorder)
1285 clrevorder[x] = len(clrevorder)
1285
1286
1286 if self._ellipses:
1287 if self._ellipses:
1287 # Only update manifests if x is going to be sent. Otherwise we
1288 # Only update manifests if x is going to be sent. Otherwise we
1288 # end up with bogus linkrevs specified for manifests and
1289 # end up with bogus linkrevs specified for manifests and
1289 # we skip some manifest nodes that we should otherwise
1290 # we skip some manifest nodes that we should otherwise
1290 # have sent.
1291 # have sent.
1291 if (
1292 if (
1292 x in self._fullclnodes
1293 x in self._fullclnodes
1293 or cl.rev(x) in self._precomputedellipsis
1294 or cl.rev(x) in self._precomputedellipsis
1294 ):
1295 ):
1295
1296
1296 manifestnode = c.manifest
1297 manifestnode = c.manifest
1297 # Record the first changeset introducing this manifest
1298 # Record the first changeset introducing this manifest
1298 # version.
1299 # version.
1299 manifests.setdefault(manifestnode, x)
1300 manifests.setdefault(manifestnode, x)
1300 # Set this narrow-specific dict so we have the lowest
1301 # Set this narrow-specific dict so we have the lowest
1301 # manifest revnum to look up for this cl revnum. (Part of
1302 # manifest revnum to look up for this cl revnum. (Part of
1302 # mapping changelog ellipsis parents to manifest ellipsis
1303 # mapping changelog ellipsis parents to manifest ellipsis
1303 # parents)
1304 # parents)
1304 clrevtomanifestrev.setdefault(
1305 clrevtomanifestrev.setdefault(
1305 cl.rev(x), mfl.rev(manifestnode)
1306 cl.rev(x), mfl.rev(manifestnode)
1306 )
1307 )
1307 # We can't trust the changed files list in the changeset if the
1308 # We can't trust the changed files list in the changeset if the
1308 # client requested a shallow clone.
1309 # client requested a shallow clone.
1309 if self._isshallow:
1310 if self._isshallow:
1310 changedfiles.update(mfl[c.manifest].read().keys())
1311 changedfiles.update(mfl[c.manifest].read().keys())
1311 else:
1312 else:
1312 changedfiles.update(c.files)
1313 changedfiles.update(c.files)
1313 else:
1314 else:
1314 # record the first changeset introducing this manifest version
1315 # record the first changeset introducing this manifest version
1315 manifests.setdefault(c.manifest, x)
1316 manifests.setdefault(c.manifest, x)
1316 # Record a complete list of potentially-changed files in
1317 # Record a complete list of potentially-changed files in
1317 # this manifest.
1318 # this manifest.
1318 changedfiles.update(c.files)
1319 changedfiles.update(c.files)
1319
1320
1320 return x
1321 return x
1321
1322
1322 gen = deltagroup(
1323 gen = deltagroup(
1323 self._repo,
1324 self._repo,
1324 cl,
1325 cl,
1325 nodes,
1326 nodes,
1326 True,
1327 True,
1327 lookupcl,
1328 lookupcl,
1328 self._forcedeltaparentprev,
1329 self._forcedeltaparentprev,
1329 ellipses=self._ellipses,
1330 ellipses=self._ellipses,
1330 topic=_(b'changesets'),
1331 topic=_(b'changesets'),
1331 clrevtolocalrev={},
1332 clrevtolocalrev={},
1332 fullclnodes=self._fullclnodes,
1333 fullclnodes=self._fullclnodes,
1333 precomputedellipsis=self._precomputedellipsis,
1334 precomputedellipsis=self._precomputedellipsis,
1334 sidedata_helpers=sidedata_helpers,
1335 sidedata_helpers=sidedata_helpers,
1335 )
1336 )
1336
1337
1337 return state, gen
1338 return state, gen
1338
1339
1339 def generatemanifests(
1340 def generatemanifests(
1340 self,
1341 self,
1341 commonrevs,
1342 commonrevs,
1342 clrevorder,
1343 clrevorder,
1343 fastpathlinkrev,
1344 fastpathlinkrev,
1344 manifests,
1345 manifests,
1345 fnodes,
1346 fnodes,
1346 source,
1347 source,
1347 clrevtolocalrev,
1348 clrevtolocalrev,
1348 sidedata_helpers=None,
1349 sidedata_helpers=None,
1349 ):
1350 ):
1350 """Returns an iterator of changegroup chunks containing manifests.
1351 """Returns an iterator of changegroup chunks containing manifests.
1351
1352
1352 `source` is unused here, but is used by extensions like remotefilelog to
1353 `source` is unused here, but is used by extensions like remotefilelog to
1353 change what is sent based in pulls vs pushes, etc.
1354 change what is sent based in pulls vs pushes, etc.
1354
1355
1355 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1356 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1356 `sidedata_helpers`.
1357 `sidedata_helpers`.
1357 """
1358 """
1358 repo = self._repo
1359 repo = self._repo
1359 mfl = repo.manifestlog
1360 mfl = repo.manifestlog
1360 tmfnodes = {b'': manifests}
1361 tmfnodes = {b'': manifests}
1361
1362
1362 # Callback for the manifest, used to collect linkrevs for filelog
1363 # Callback for the manifest, used to collect linkrevs for filelog
1363 # revisions.
1364 # revisions.
1364 # Returns the linkrev node (collected in lookupcl).
1365 # Returns the linkrev node (collected in lookupcl).
1365 def makelookupmflinknode(tree, nodes):
1366 def makelookupmflinknode(tree, nodes):
1366 if fastpathlinkrev:
1367 if fastpathlinkrev:
1367 assert not tree
1368 assert not tree
1368
1369
1369 # pytype: disable=unsupported-operands
1370 # pytype: disable=unsupported-operands
1370 return manifests.__getitem__
1371 return manifests.__getitem__
1371 # pytype: enable=unsupported-operands
1372 # pytype: enable=unsupported-operands
1372
1373
1373 def lookupmflinknode(x):
1374 def lookupmflinknode(x):
1374 """Callback for looking up the linknode for manifests.
1375 """Callback for looking up the linknode for manifests.
1375
1376
1376 Returns the linkrev node for the specified manifest.
1377 Returns the linkrev node for the specified manifest.
1377
1378
1378 SIDE EFFECT:
1379 SIDE EFFECT:
1379
1380
1380 1) fclnodes gets populated with the list of relevant
1381 1) fclnodes gets populated with the list of relevant
1381 file nodes if we're not using fastpathlinkrev
1382 file nodes if we're not using fastpathlinkrev
1382 2) When treemanifests are in use, collects treemanifest nodes
1383 2) When treemanifests are in use, collects treemanifest nodes
1383 to send
1384 to send
1384
1385
1385 Note that this means manifests must be completely sent to
1386 Note that this means manifests must be completely sent to
1386 the client before you can trust the list of files and
1387 the client before you can trust the list of files and
1387 treemanifests to send.
1388 treemanifests to send.
1388 """
1389 """
1389 clnode = nodes[x]
1390 clnode = nodes[x]
1390 mdata = mfl.get(tree, x).readfast(shallow=True)
1391 mdata = mfl.get(tree, x).readfast(shallow=True)
1391 for p, n, fl in mdata.iterentries():
1392 for p, n, fl in mdata.iterentries():
1392 if fl == b't': # subdirectory manifest
1393 if fl == b't': # subdirectory manifest
1393 subtree = tree + p + b'/'
1394 subtree = tree + p + b'/'
1394 tmfclnodes = tmfnodes.setdefault(subtree, {})
1395 tmfclnodes = tmfnodes.setdefault(subtree, {})
1395 tmfclnode = tmfclnodes.setdefault(n, clnode)
1396 tmfclnode = tmfclnodes.setdefault(n, clnode)
1396 if clrevorder[clnode] < clrevorder[tmfclnode]:
1397 if clrevorder[clnode] < clrevorder[tmfclnode]:
1397 tmfclnodes[n] = clnode
1398 tmfclnodes[n] = clnode
1398 else:
1399 else:
1399 f = tree + p
1400 f = tree + p
1400 fclnodes = fnodes.setdefault(f, {})
1401 fclnodes = fnodes.setdefault(f, {})
1401 fclnode = fclnodes.setdefault(n, clnode)
1402 fclnode = fclnodes.setdefault(n, clnode)
1402 if clrevorder[clnode] < clrevorder[fclnode]:
1403 if clrevorder[clnode] < clrevorder[fclnode]:
1403 fclnodes[n] = clnode
1404 fclnodes[n] = clnode
1404 return clnode
1405 return clnode
1405
1406
1406 return lookupmflinknode
1407 return lookupmflinknode
1407
1408
1408 while tmfnodes:
1409 while tmfnodes:
1409 tree, nodes = tmfnodes.popitem()
1410 tree, nodes = tmfnodes.popitem()
1410
1411
1411 should_visit = self._matcher.visitdir(tree[:-1])
1412 should_visit = self._matcher.visitdir(tree[:-1])
1412 if tree and not should_visit:
1413 if tree and not should_visit:
1413 continue
1414 continue
1414
1415
1415 store = mfl.getstorage(tree)
1416 store = mfl.getstorage(tree)
1416
1417
1417 if not should_visit:
1418 if not should_visit:
1418 # No nodes to send because this directory is out of
1419 # No nodes to send because this directory is out of
1419 # the client's view of the repository (probably
1420 # the client's view of the repository (probably
1420 # because of narrow clones). Do this even for the root
1421 # because of narrow clones). Do this even for the root
1421 # directory (tree=='')
1422 # directory (tree=='')
1422 prunednodes = []
1423 prunednodes = []
1423 else:
1424 else:
1424 # Avoid sending any manifest nodes we can prove the
1425 # Avoid sending any manifest nodes we can prove the
1425 # client already has by checking linkrevs. See the
1426 # client already has by checking linkrevs. See the
1426 # related comment in generatefiles().
1427 # related comment in generatefiles().
1427 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1428 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1428
1429
1429 if tree and not prunednodes:
1430 if tree and not prunednodes:
1430 continue
1431 continue
1431
1432
1432 lookupfn = makelookupmflinknode(tree, nodes)
1433 lookupfn = makelookupmflinknode(tree, nodes)
1433
1434
1434 deltas = deltagroup(
1435 deltas = deltagroup(
1435 self._repo,
1436 self._repo,
1436 store,
1437 store,
1437 prunednodes,
1438 prunednodes,
1438 False,
1439 False,
1439 lookupfn,
1440 lookupfn,
1440 self._forcedeltaparentprev,
1441 self._forcedeltaparentprev,
1441 ellipses=self._ellipses,
1442 ellipses=self._ellipses,
1442 topic=_(b'manifests'),
1443 topic=_(b'manifests'),
1443 clrevtolocalrev=clrevtolocalrev,
1444 clrevtolocalrev=clrevtolocalrev,
1444 fullclnodes=self._fullclnodes,
1445 fullclnodes=self._fullclnodes,
1445 precomputedellipsis=self._precomputedellipsis,
1446 precomputedellipsis=self._precomputedellipsis,
1446 sidedata_helpers=sidedata_helpers,
1447 sidedata_helpers=sidedata_helpers,
1447 )
1448 )
1448
1449
1449 if not self._oldmatcher.visitdir(store.tree[:-1]):
1450 if not self._oldmatcher.visitdir(store.tree[:-1]):
1450 yield tree, deltas
1451 yield tree, deltas
1451 else:
1452 else:
1452 # 'deltas' is a generator and we need to consume it even if
1453 # 'deltas' is a generator and we need to consume it even if
1453 # we are not going to send it because a side-effect is that
1454 # we are not going to send it because a side-effect is that
1454 # it updates tmdnodes (via lookupfn)
1455 # it updates tmdnodes (via lookupfn)
1455 for d in deltas:
1456 for d in deltas:
1456 pass
1457 pass
1457 if not tree:
1458 if not tree:
1458 yield tree, []
1459 yield tree, []
1459
1460
1460 def _prunemanifests(self, store, nodes, commonrevs):
1461 def _prunemanifests(self, store, nodes, commonrevs):
1461 if not self._ellipses:
1462 if not self._ellipses:
1462 # In non-ellipses case and large repositories, it is better to
1463 # In non-ellipses case and large repositories, it is better to
1463 # prevent calling of store.rev and store.linkrev on a lot of
1464 # prevent calling of store.rev and store.linkrev on a lot of
1464 # nodes as compared to sending some extra data
1465 # nodes as compared to sending some extra data
1465 return nodes.copy()
1466 return nodes.copy()
1466 # This is split out as a separate method to allow filtering
1467 # This is split out as a separate method to allow filtering
1467 # commonrevs in extension code.
1468 # commonrevs in extension code.
1468 #
1469 #
1469 # TODO(augie): this shouldn't be required, instead we should
1470 # TODO(augie): this shouldn't be required, instead we should
1470 # make filtering of revisions to send delegated to the store
1471 # make filtering of revisions to send delegated to the store
1471 # layer.
1472 # layer.
1472 frev, flr = store.rev, store.linkrev
1473 frev, flr = store.rev, store.linkrev
1473 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1474 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1474
1475
1475 # The 'source' parameter is useful for extensions
1476 # The 'source' parameter is useful for extensions
1476 def generatefiles(
1477 def generatefiles(
1477 self,
1478 self,
1478 changedfiles,
1479 changedfiles,
1479 commonrevs,
1480 commonrevs,
1480 source,
1481 source,
1481 mfdicts,
1482 mfdicts,
1482 fastpathlinkrev,
1483 fastpathlinkrev,
1483 fnodes,
1484 fnodes,
1484 clrevs,
1485 clrevs,
1485 sidedata_helpers=None,
1486 sidedata_helpers=None,
1486 ):
1487 ):
1487 changedfiles = [
1488 changedfiles = [
1488 f
1489 f
1489 for f in changedfiles
1490 for f in changedfiles
1490 if self._matcher(f) and not self._oldmatcher(f)
1491 if self._matcher(f) and not self._oldmatcher(f)
1491 ]
1492 ]
1492
1493
1493 if not fastpathlinkrev:
1494 if not fastpathlinkrev:
1494
1495
1495 def normallinknodes(unused, fname):
1496 def normallinknodes(unused, fname):
1496 return fnodes.get(fname, {})
1497 return fnodes.get(fname, {})
1497
1498
1498 else:
1499 else:
1499 cln = self._repo.changelog.node
1500 cln = self._repo.changelog.node
1500
1501
1501 def normallinknodes(store, fname):
1502 def normallinknodes(store, fname):
1502 flinkrev = store.linkrev
1503 flinkrev = store.linkrev
1503 fnode = store.node
1504 fnode = store.node
1504 revs = ((r, flinkrev(r)) for r in store)
1505 revs = ((r, flinkrev(r)) for r in store)
1505 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1506 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1506
1507
1507 clrevtolocalrev = {}
1508 clrevtolocalrev = {}
1508
1509
1509 if self._isshallow:
1510 if self._isshallow:
1510 # In a shallow clone, the linknodes callback needs to also include
1511 # In a shallow clone, the linknodes callback needs to also include
1511 # those file nodes that are in the manifests we sent but weren't
1512 # those file nodes that are in the manifests we sent but weren't
1512 # introduced by those manifests.
1513 # introduced by those manifests.
1513 commonctxs = [self._repo[c] for c in commonrevs]
1514 commonctxs = [self._repo[c] for c in commonrevs]
1514 clrev = self._repo.changelog.rev
1515 clrev = self._repo.changelog.rev
1515
1516
1516 def linknodes(flog, fname):
1517 def linknodes(flog, fname):
1517 for c in commonctxs:
1518 for c in commonctxs:
1518 try:
1519 try:
1519 fnode = c.filenode(fname)
1520 fnode = c.filenode(fname)
1520 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1521 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1521 except error.ManifestLookupError:
1522 except error.ManifestLookupError:
1522 pass
1523 pass
1523 links = normallinknodes(flog, fname)
1524 links = normallinknodes(flog, fname)
1524 if len(links) != len(mfdicts):
1525 if len(links) != len(mfdicts):
1525 for mf, lr in mfdicts:
1526 for mf, lr in mfdicts:
1526 fnode = mf.get(fname, None)
1527 fnode = mf.get(fname, None)
1527 if fnode in links:
1528 if fnode in links:
1528 links[fnode] = min(links[fnode], lr, key=clrev)
1529 links[fnode] = min(links[fnode], lr, key=clrev)
1529 elif fnode:
1530 elif fnode:
1530 links[fnode] = lr
1531 links[fnode] = lr
1531 return links
1532 return links
1532
1533
1533 else:
1534 else:
1534 linknodes = normallinknodes
1535 linknodes = normallinknodes
1535
1536
1536 repo = self._repo
1537 repo = self._repo
1537 progress = repo.ui.makeprogress(
1538 progress = repo.ui.makeprogress(
1538 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1539 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1539 )
1540 )
1540 for i, fname in enumerate(sorted(changedfiles)):
1541 for i, fname in enumerate(sorted(changedfiles)):
1541 filerevlog = repo.file(fname)
1542 filerevlog = repo.file(fname)
1542 if not filerevlog:
1543 if not filerevlog:
1543 raise error.Abort(
1544 raise error.Abort(
1544 _(b"empty or missing file data for %s") % fname
1545 _(b"empty or missing file data for %s") % fname
1545 )
1546 )
1546
1547
1547 clrevtolocalrev.clear()
1548 clrevtolocalrev.clear()
1548
1549
1549 linkrevnodes = linknodes(filerevlog, fname)
1550 linkrevnodes = linknodes(filerevlog, fname)
1550 # Lookup for filenodes, we collected the linkrev nodes above in the
1551 # Lookup for filenodes, we collected the linkrev nodes above in the
1551 # fastpath case and with lookupmf in the slowpath case.
1552 # fastpath case and with lookupmf in the slowpath case.
1552 def lookupfilelog(x):
1553 def lookupfilelog(x):
1553 return linkrevnodes[x]
1554 return linkrevnodes[x]
1554
1555
1555 frev, flr = filerevlog.rev, filerevlog.linkrev
1556 frev, flr = filerevlog.rev, filerevlog.linkrev
1556 # Skip sending any filenode we know the client already
1557 # Skip sending any filenode we know the client already
1557 # has. This avoids over-sending files relatively
1558 # has. This avoids over-sending files relatively
1558 # inexpensively, so it's not a problem if we under-filter
1559 # inexpensively, so it's not a problem if we under-filter
1559 # here.
1560 # here.
1560 filenodes = [
1561 filenodes = [
1561 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1562 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1562 ]
1563 ]
1563
1564
1564 if not filenodes:
1565 if not filenodes:
1565 continue
1566 continue
1566
1567
1567 progress.update(i + 1, item=fname)
1568 progress.update(i + 1, item=fname)
1568
1569
1569 deltas = deltagroup(
1570 deltas = deltagroup(
1570 self._repo,
1571 self._repo,
1571 filerevlog,
1572 filerevlog,
1572 filenodes,
1573 filenodes,
1573 False,
1574 False,
1574 lookupfilelog,
1575 lookupfilelog,
1575 self._forcedeltaparentprev,
1576 self._forcedeltaparentprev,
1576 ellipses=self._ellipses,
1577 ellipses=self._ellipses,
1577 clrevtolocalrev=clrevtolocalrev,
1578 clrevtolocalrev=clrevtolocalrev,
1578 fullclnodes=self._fullclnodes,
1579 fullclnodes=self._fullclnodes,
1579 precomputedellipsis=self._precomputedellipsis,
1580 precomputedellipsis=self._precomputedellipsis,
1580 sidedata_helpers=sidedata_helpers,
1581 sidedata_helpers=sidedata_helpers,
1581 )
1582 )
1582
1583
1583 yield fname, deltas
1584 yield fname, deltas
1584
1585
1585 progress.complete()
1586 progress.complete()
1586
1587
1587
1588
1588 def _makecg1packer(
1589 def _makecg1packer(
1589 repo,
1590 repo,
1590 oldmatcher,
1591 oldmatcher,
1591 matcher,
1592 matcher,
1592 bundlecaps,
1593 bundlecaps,
1593 ellipses=False,
1594 ellipses=False,
1594 shallow=False,
1595 shallow=False,
1595 ellipsisroots=None,
1596 ellipsisroots=None,
1596 fullnodes=None,
1597 fullnodes=None,
1597 remote_sidedata=None,
1598 remote_sidedata=None,
1598 ):
1599 ):
1599 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1600 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1600 d.node, d.p1node, d.p2node, d.linknode
1601 d.node, d.p1node, d.p2node, d.linknode
1601 )
1602 )
1602
1603
1603 return cgpacker(
1604 return cgpacker(
1604 repo,
1605 repo,
1605 oldmatcher,
1606 oldmatcher,
1606 matcher,
1607 matcher,
1607 b'01',
1608 b'01',
1608 builddeltaheader=builddeltaheader,
1609 builddeltaheader=builddeltaheader,
1609 manifestsend=b'',
1610 manifestsend=b'',
1610 forcedeltaparentprev=True,
1611 forcedeltaparentprev=True,
1611 bundlecaps=bundlecaps,
1612 bundlecaps=bundlecaps,
1612 ellipses=ellipses,
1613 ellipses=ellipses,
1613 shallow=shallow,
1614 shallow=shallow,
1614 ellipsisroots=ellipsisroots,
1615 ellipsisroots=ellipsisroots,
1615 fullnodes=fullnodes,
1616 fullnodes=fullnodes,
1616 )
1617 )
1617
1618
1618
1619
1619 def _makecg2packer(
1620 def _makecg2packer(
1620 repo,
1621 repo,
1621 oldmatcher,
1622 oldmatcher,
1622 matcher,
1623 matcher,
1623 bundlecaps,
1624 bundlecaps,
1624 ellipses=False,
1625 ellipses=False,
1625 shallow=False,
1626 shallow=False,
1626 ellipsisroots=None,
1627 ellipsisroots=None,
1627 fullnodes=None,
1628 fullnodes=None,
1628 remote_sidedata=None,
1629 remote_sidedata=None,
1629 ):
1630 ):
1630 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1631 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1631 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1632 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1632 )
1633 )
1633
1634
1634 return cgpacker(
1635 return cgpacker(
1635 repo,
1636 repo,
1636 oldmatcher,
1637 oldmatcher,
1637 matcher,
1638 matcher,
1638 b'02',
1639 b'02',
1639 builddeltaheader=builddeltaheader,
1640 builddeltaheader=builddeltaheader,
1640 manifestsend=b'',
1641 manifestsend=b'',
1641 bundlecaps=bundlecaps,
1642 bundlecaps=bundlecaps,
1642 ellipses=ellipses,
1643 ellipses=ellipses,
1643 shallow=shallow,
1644 shallow=shallow,
1644 ellipsisroots=ellipsisroots,
1645 ellipsisroots=ellipsisroots,
1645 fullnodes=fullnodes,
1646 fullnodes=fullnodes,
1646 )
1647 )
1647
1648
1648
1649
1649 def _makecg3packer(
1650 def _makecg3packer(
1650 repo,
1651 repo,
1651 oldmatcher,
1652 oldmatcher,
1652 matcher,
1653 matcher,
1653 bundlecaps,
1654 bundlecaps,
1654 ellipses=False,
1655 ellipses=False,
1655 shallow=False,
1656 shallow=False,
1656 ellipsisroots=None,
1657 ellipsisroots=None,
1657 fullnodes=None,
1658 fullnodes=None,
1658 remote_sidedata=None,
1659 remote_sidedata=None,
1659 ):
1660 ):
1660 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1661 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1661 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1662 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1662 )
1663 )
1663
1664
1664 return cgpacker(
1665 return cgpacker(
1665 repo,
1666 repo,
1666 oldmatcher,
1667 oldmatcher,
1667 matcher,
1668 matcher,
1668 b'03',
1669 b'03',
1669 builddeltaheader=builddeltaheader,
1670 builddeltaheader=builddeltaheader,
1670 manifestsend=closechunk(),
1671 manifestsend=closechunk(),
1671 bundlecaps=bundlecaps,
1672 bundlecaps=bundlecaps,
1672 ellipses=ellipses,
1673 ellipses=ellipses,
1673 shallow=shallow,
1674 shallow=shallow,
1674 ellipsisroots=ellipsisroots,
1675 ellipsisroots=ellipsisroots,
1675 fullnodes=fullnodes,
1676 fullnodes=fullnodes,
1676 )
1677 )
1677
1678
1678
1679
1679 def _makecg4packer(
1680 def _makecg4packer(
1680 repo,
1681 repo,
1681 oldmatcher,
1682 oldmatcher,
1682 matcher,
1683 matcher,
1683 bundlecaps,
1684 bundlecaps,
1684 ellipses=False,
1685 ellipses=False,
1685 shallow=False,
1686 shallow=False,
1686 ellipsisroots=None,
1687 ellipsisroots=None,
1687 fullnodes=None,
1688 fullnodes=None,
1688 remote_sidedata=None,
1689 remote_sidedata=None,
1689 ):
1690 ):
1690 # Sidedata is in a separate chunk from the delta to differentiate
1691 # Sidedata is in a separate chunk from the delta to differentiate
1691 # "raw delta" and sidedata.
1692 # "raw delta" and sidedata.
1692 def builddeltaheader(d):
1693 def builddeltaheader(d):
1693 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1694 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1694 d.protocol_flags,
1695 d.protocol_flags,
1695 d.node,
1696 d.node,
1696 d.p1node,
1697 d.p1node,
1697 d.p2node,
1698 d.p2node,
1698 d.basenode,
1699 d.basenode,
1699 d.linknode,
1700 d.linknode,
1700 d.flags,
1701 d.flags,
1701 )
1702 )
1702
1703
1703 return cgpacker(
1704 return cgpacker(
1704 repo,
1705 repo,
1705 oldmatcher,
1706 oldmatcher,
1706 matcher,
1707 matcher,
1707 b'04',
1708 b'04',
1708 builddeltaheader=builddeltaheader,
1709 builddeltaheader=builddeltaheader,
1709 manifestsend=closechunk(),
1710 manifestsend=closechunk(),
1710 bundlecaps=bundlecaps,
1711 bundlecaps=bundlecaps,
1711 ellipses=ellipses,
1712 ellipses=ellipses,
1712 shallow=shallow,
1713 shallow=shallow,
1713 ellipsisroots=ellipsisroots,
1714 ellipsisroots=ellipsisroots,
1714 fullnodes=fullnodes,
1715 fullnodes=fullnodes,
1715 remote_sidedata=remote_sidedata,
1716 remote_sidedata=remote_sidedata,
1716 )
1717 )
1717
1718
1718
1719
1719 _packermap = {
1720 _packermap = {
1720 b'01': (_makecg1packer, cg1unpacker),
1721 b'01': (_makecg1packer, cg1unpacker),
1721 # cg2 adds support for exchanging generaldelta
1722 # cg2 adds support for exchanging generaldelta
1722 b'02': (_makecg2packer, cg2unpacker),
1723 b'02': (_makecg2packer, cg2unpacker),
1723 # cg3 adds support for exchanging revlog flags and treemanifests
1724 # cg3 adds support for exchanging revlog flags and treemanifests
1724 b'03': (_makecg3packer, cg3unpacker),
1725 b'03': (_makecg3packer, cg3unpacker),
1725 # ch4 adds support for exchanging sidedata
1726 # ch4 adds support for exchanging sidedata
1726 b'04': (_makecg4packer, cg4unpacker),
1727 b'04': (_makecg4packer, cg4unpacker),
1727 }
1728 }
1728
1729
1729
1730
1730 def allsupportedversions(repo):
1731 def allsupportedversions(repo):
1731 versions = set(_packermap.keys())
1732 versions = set(_packermap.keys())
1732 needv03 = False
1733 needv03 = False
1733 if (
1734 if (
1734 repo.ui.configbool(b'experimental', b'changegroup3')
1735 repo.ui.configbool(b'experimental', b'changegroup3')
1735 or repo.ui.configbool(b'experimental', b'treemanifest')
1736 or repo.ui.configbool(b'experimental', b'treemanifest')
1736 or scmutil.istreemanifest(repo)
1737 or scmutil.istreemanifest(repo)
1737 ):
1738 ):
1738 # we keep version 03 because we need to to exchange treemanifest data
1739 # we keep version 03 because we need to to exchange treemanifest data
1739 #
1740 #
1740 # we also keep vresion 01 and 02, because it is possible for repo to
1741 # we also keep vresion 01 and 02, because it is possible for repo to
1741 # contains both normal and tree manifest at the same time. so using
1742 # contains both normal and tree manifest at the same time. so using
1742 # older version to pull data is viable
1743 # older version to pull data is viable
1743 #
1744 #
1744 # (or even to push subset of history)
1745 # (or even to push subset of history)
1745 needv03 = True
1746 needv03 = True
1746 if not needv03:
1747 if not needv03:
1747 versions.discard(b'03')
1748 versions.discard(b'03')
1748 want_v4 = (
1749 want_v4 = (
1749 repo.ui.configbool(b'experimental', b'changegroup4')
1750 repo.ui.configbool(b'experimental', b'changegroup4')
1750 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1751 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1751 or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
1752 or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
1752 )
1753 )
1753 if not want_v4:
1754 if not want_v4:
1754 versions.discard(b'04')
1755 versions.discard(b'04')
1755 return versions
1756 return versions
1756
1757
1757
1758
1758 # Changegroup versions that can be applied to the repo
1759 # Changegroup versions that can be applied to the repo
1759 def supportedincomingversions(repo):
1760 def supportedincomingversions(repo):
1760 return allsupportedversions(repo)
1761 return allsupportedversions(repo)
1761
1762
1762
1763
1763 # Changegroup versions that can be created from the repo
1764 # Changegroup versions that can be created from the repo
1764 def supportedoutgoingversions(repo):
1765 def supportedoutgoingversions(repo):
1765 versions = allsupportedversions(repo)
1766 versions = allsupportedversions(repo)
1766 if scmutil.istreemanifest(repo):
1767 if scmutil.istreemanifest(repo):
1767 # Versions 01 and 02 support only flat manifests and it's just too
1768 # Versions 01 and 02 support only flat manifests and it's just too
1768 # expensive to convert between the flat manifest and tree manifest on
1769 # expensive to convert between the flat manifest and tree manifest on
1769 # the fly. Since tree manifests are hashed differently, all of history
1770 # the fly. Since tree manifests are hashed differently, all of history
1770 # would have to be converted. Instead, we simply don't even pretend to
1771 # would have to be converted. Instead, we simply don't even pretend to
1771 # support versions 01 and 02.
1772 # support versions 01 and 02.
1772 versions.discard(b'01')
1773 versions.discard(b'01')
1773 versions.discard(b'02')
1774 versions.discard(b'02')
1774 if requirements.NARROW_REQUIREMENT in repo.requirements:
1775 if requirements.NARROW_REQUIREMENT in repo.requirements:
1775 # Versions 01 and 02 don't support revlog flags, and we need to
1776 # Versions 01 and 02 don't support revlog flags, and we need to
1776 # support that for stripping and unbundling to work.
1777 # support that for stripping and unbundling to work.
1777 versions.discard(b'01')
1778 versions.discard(b'01')
1778 versions.discard(b'02')
1779 versions.discard(b'02')
1779 if LFS_REQUIREMENT in repo.requirements:
1780 if LFS_REQUIREMENT in repo.requirements:
1780 # Versions 01 and 02 don't support revlog flags, and we need to
1781 # Versions 01 and 02 don't support revlog flags, and we need to
1781 # mark LFS entries with REVIDX_EXTSTORED.
1782 # mark LFS entries with REVIDX_EXTSTORED.
1782 versions.discard(b'01')
1783 versions.discard(b'01')
1783 versions.discard(b'02')
1784 versions.discard(b'02')
1784
1785
1785 return versions
1786 return versions
1786
1787
1787
1788
1788 def localversion(repo):
1789 def localversion(repo):
1789 # Finds the best version to use for bundles that are meant to be used
1790 # Finds the best version to use for bundles that are meant to be used
1790 # locally, such as those from strip and shelve, and temporary bundles.
1791 # locally, such as those from strip and shelve, and temporary bundles.
1791 return max(supportedoutgoingversions(repo))
1792 return max(supportedoutgoingversions(repo))
1792
1793
1793
1794
1794 def safeversion(repo):
1795 def safeversion(repo):
1795 # Finds the smallest version that it's safe to assume clients of the repo
1796 # Finds the smallest version that it's safe to assume clients of the repo
1796 # will support. For example, all hg versions that support generaldelta also
1797 # will support. For example, all hg versions that support generaldelta also
1797 # support changegroup 02.
1798 # support changegroup 02.
1798 versions = supportedoutgoingversions(repo)
1799 versions = supportedoutgoingversions(repo)
1799 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1800 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1800 versions.discard(b'01')
1801 versions.discard(b'01')
1801 assert versions
1802 assert versions
1802 return min(versions)
1803 return min(versions)
1803
1804
1804
1805
1805 def getbundler(
1806 def getbundler(
1806 version,
1807 version,
1807 repo,
1808 repo,
1808 bundlecaps=None,
1809 bundlecaps=None,
1809 oldmatcher=None,
1810 oldmatcher=None,
1810 matcher=None,
1811 matcher=None,
1811 ellipses=False,
1812 ellipses=False,
1812 shallow=False,
1813 shallow=False,
1813 ellipsisroots=None,
1814 ellipsisroots=None,
1814 fullnodes=None,
1815 fullnodes=None,
1815 remote_sidedata=None,
1816 remote_sidedata=None,
1816 ):
1817 ):
1817 assert version in supportedoutgoingversions(repo)
1818 assert version in supportedoutgoingversions(repo)
1818
1819
1819 if matcher is None:
1820 if matcher is None:
1820 matcher = matchmod.always()
1821 matcher = matchmod.always()
1821 if oldmatcher is None:
1822 if oldmatcher is None:
1822 oldmatcher = matchmod.never()
1823 oldmatcher = matchmod.never()
1823
1824
1824 if version == b'01' and not matcher.always():
1825 if version == b'01' and not matcher.always():
1825 raise error.ProgrammingError(
1826 raise error.ProgrammingError(
1826 b'version 01 changegroups do not support sparse file matchers'
1827 b'version 01 changegroups do not support sparse file matchers'
1827 )
1828 )
1828
1829
1829 if ellipses and version in (b'01', b'02'):
1830 if ellipses and version in (b'01', b'02'):
1830 raise error.Abort(
1831 raise error.Abort(
1831 _(
1832 _(
1832 b'ellipsis nodes require at least cg3 on client and server, '
1833 b'ellipsis nodes require at least cg3 on client and server, '
1833 b'but negotiated version %s'
1834 b'but negotiated version %s'
1834 )
1835 )
1835 % version
1836 % version
1836 )
1837 )
1837
1838
1838 # Requested files could include files not in the local store. So
1839 # Requested files could include files not in the local store. So
1839 # filter those out.
1840 # filter those out.
1840 matcher = repo.narrowmatch(matcher)
1841 matcher = repo.narrowmatch(matcher)
1841
1842
1842 fn = _packermap[version][0]
1843 fn = _packermap[version][0]
1843 return fn(
1844 return fn(
1844 repo,
1845 repo,
1845 oldmatcher,
1846 oldmatcher,
1846 matcher,
1847 matcher,
1847 bundlecaps,
1848 bundlecaps,
1848 ellipses=ellipses,
1849 ellipses=ellipses,
1849 shallow=shallow,
1850 shallow=shallow,
1850 ellipsisroots=ellipsisroots,
1851 ellipsisroots=ellipsisroots,
1851 fullnodes=fullnodes,
1852 fullnodes=fullnodes,
1852 remote_sidedata=remote_sidedata,
1853 remote_sidedata=remote_sidedata,
1853 )
1854 )
1854
1855
1855
1856
1856 def getunbundler(version, fh, alg, extras=None):
1857 def getunbundler(version, fh, alg, extras=None):
1857 return _packermap[version][1](fh, alg, extras=extras)
1858 return _packermap[version][1](fh, alg, extras=extras)
1858
1859
1859
1860
1860 def _changegroupinfo(repo, nodes, source):
1861 def _changegroupinfo(repo, nodes, source):
1861 if repo.ui.verbose or source == b'bundle':
1862 if repo.ui.verbose or source == b'bundle':
1862 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1863 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1863 if repo.ui.debugflag:
1864 if repo.ui.debugflag:
1864 repo.ui.debug(b"list of changesets:\n")
1865 repo.ui.debug(b"list of changesets:\n")
1865 for node in nodes:
1866 for node in nodes:
1866 repo.ui.debug(b"%s\n" % hex(node))
1867 repo.ui.debug(b"%s\n" % hex(node))
1867
1868
1868
1869
1869 def makechangegroup(
1870 def makechangegroup(
1870 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1871 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1871 ):
1872 ):
1872 cgstream = makestream(
1873 cgstream = makestream(
1873 repo,
1874 repo,
1874 outgoing,
1875 outgoing,
1875 version,
1876 version,
1876 source,
1877 source,
1877 fastpath=fastpath,
1878 fastpath=fastpath,
1878 bundlecaps=bundlecaps,
1879 bundlecaps=bundlecaps,
1879 )
1880 )
1880 return getunbundler(
1881 return getunbundler(
1881 version,
1882 version,
1882 util.chunkbuffer(cgstream),
1883 util.chunkbuffer(cgstream),
1883 None,
1884 None,
1884 {b'clcount': len(outgoing.missing)},
1885 {b'clcount': len(outgoing.missing)},
1885 )
1886 )
1886
1887
1887
1888
1888 def makestream(
1889 def makestream(
1889 repo,
1890 repo,
1890 outgoing,
1891 outgoing,
1891 version,
1892 version,
1892 source,
1893 source,
1893 fastpath=False,
1894 fastpath=False,
1894 bundlecaps=None,
1895 bundlecaps=None,
1895 matcher=None,
1896 matcher=None,
1896 remote_sidedata=None,
1897 remote_sidedata=None,
1897 ):
1898 ):
1898 bundler = getbundler(
1899 bundler = getbundler(
1899 version,
1900 version,
1900 repo,
1901 repo,
1901 bundlecaps=bundlecaps,
1902 bundlecaps=bundlecaps,
1902 matcher=matcher,
1903 matcher=matcher,
1903 remote_sidedata=remote_sidedata,
1904 remote_sidedata=remote_sidedata,
1904 )
1905 )
1905
1906
1906 repo = repo.unfiltered()
1907 repo = repo.unfiltered()
1907 commonrevs = outgoing.common
1908 commonrevs = outgoing.common
1908 csets = outgoing.missing
1909 csets = outgoing.missing
1909 heads = outgoing.ancestorsof
1910 heads = outgoing.ancestorsof
1910 # We go through the fast path if we get told to, or if all (unfiltered
1911 # We go through the fast path if we get told to, or if all (unfiltered
1911 # heads have been requested (since we then know there all linkrevs will
1912 # heads have been requested (since we then know there all linkrevs will
1912 # be pulled by the client).
1913 # be pulled by the client).
1913 heads.sort()
1914 heads.sort()
1914 fastpathlinkrev = fastpath or (
1915 fastpathlinkrev = fastpath or (
1915 repo.filtername is None and heads == sorted(repo.heads())
1916 repo.filtername is None and heads == sorted(repo.heads())
1916 )
1917 )
1917
1918
1918 repo.hook(b'preoutgoing', throw=True, source=source)
1919 repo.hook(b'preoutgoing', throw=True, source=source)
1919 _changegroupinfo(repo, csets, source)
1920 _changegroupinfo(repo, csets, source)
1920 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1921 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1921
1922
1922
1923
1923 def _addchangegroupfiles(
1924 def _addchangegroupfiles(
1924 repo,
1925 repo,
1925 source,
1926 source,
1926 revmap,
1927 revmap,
1927 trp,
1928 trp,
1928 expectedfiles,
1929 expectedfiles,
1929 needfiles,
1930 needfiles,
1930 addrevisioncb=None,
1931 addrevisioncb=None,
1931 ):
1932 ):
1932 revisions = 0
1933 revisions = 0
1933 files = 0
1934 files = 0
1934 progress = repo.ui.makeprogress(
1935 progress = repo.ui.makeprogress(
1935 _(b'files'), unit=_(b'files'), total=expectedfiles
1936 _(b'files'), unit=_(b'files'), total=expectedfiles
1936 )
1937 )
1937 for chunkdata in iter(source.filelogheader, {}):
1938 for chunkdata in iter(source.filelogheader, {}):
1938 files += 1
1939 files += 1
1939 f = chunkdata[b"filename"]
1940 f = chunkdata[b"filename"]
1940 repo.ui.debug(b"adding %s revisions\n" % f)
1941 repo.ui.debug(b"adding %s revisions\n" % f)
1941 progress.increment()
1942 progress.increment()
1942 fl = repo.file(f)
1943 fl = repo.file(f)
1943 o = len(fl)
1944 o = len(fl)
1944 try:
1945 try:
1945 deltas = source.deltaiter()
1946 deltas = source.deltaiter()
1946 added = fl.addgroup(
1947 added = fl.addgroup(
1947 deltas,
1948 deltas,
1948 revmap,
1949 revmap,
1949 trp,
1950 trp,
1950 addrevisioncb=addrevisioncb,
1951 addrevisioncb=addrevisioncb,
1951 )
1952 )
1952 if not added:
1953 if not added:
1953 raise error.Abort(_(b"received file revlog group is empty"))
1954 raise error.Abort(_(b"received file revlog group is empty"))
1954 except error.CensoredBaseError as e:
1955 except error.CensoredBaseError as e:
1955 raise error.Abort(_(b"received delta base is censored: %s") % e)
1956 raise error.Abort(_(b"received delta base is censored: %s") % e)
1956 revisions += len(fl) - o
1957 revisions += len(fl) - o
1957 if f in needfiles:
1958 if f in needfiles:
1958 needs = needfiles[f]
1959 needs = needfiles[f]
1959 for new in pycompat.xrange(o, len(fl)):
1960 for new in pycompat.xrange(o, len(fl)):
1960 n = fl.node(new)
1961 n = fl.node(new)
1961 if n in needs:
1962 if n in needs:
1962 needs.remove(n)
1963 needs.remove(n)
1963 else:
1964 else:
1964 raise error.Abort(_(b"received spurious file revlog entry"))
1965 raise error.Abort(_(b"received spurious file revlog entry"))
1965 if not needs:
1966 if not needs:
1966 del needfiles[f]
1967 del needfiles[f]
1967 progress.complete()
1968 progress.complete()
1968
1969
1969 for f, needs in pycompat.iteritems(needfiles):
1970 for f, needs in pycompat.iteritems(needfiles):
1970 fl = repo.file(f)
1971 fl = repo.file(f)
1971 for n in needs:
1972 for n in needs:
1972 try:
1973 try:
1973 fl.rev(n)
1974 fl.rev(n)
1974 except error.LookupError:
1975 except error.LookupError:
1975 raise error.Abort(
1976 raise error.Abort(
1976 _(b'missing file data for %s:%s - run hg verify')
1977 _(b'missing file data for %s:%s - run hg verify')
1977 % (f, hex(n))
1978 % (f, hex(n))
1978 )
1979 )
1979
1980
1980 return revisions, files
1981 return revisions, files
@@ -1,102 +1,100 b''
1 #require pytype py3 slow
1 #require pytype py3 slow
2
2
3 $ cd $RUNTESTDIR/..
3 $ cd $RUNTESTDIR/..
4
4
5 Many of the individual files that are excluded here confuse pytype
5 Many of the individual files that are excluded here confuse pytype
6 because they do a mix of Python 2 and Python 3 things
6 because they do a mix of Python 2 and Python 3 things
7 conditionally. There's no good way to help it out with that as far as
7 conditionally. There's no good way to help it out with that as far as
8 I can tell, so let's just hide those files from it for now. We should
8 I can tell, so let's just hide those files from it for now. We should
9 endeavor to empty this list out over time, as some of these are
9 endeavor to empty this list out over time, as some of these are
10 probably hiding real problems.
10 probably hiding real problems.
11
11
12 mercurial/bundlerepo.py # no vfs and ui attrs on bundlerepo
12 mercurial/bundlerepo.py # no vfs and ui attrs on bundlerepo
13 mercurial/changegroup.py # mysterious incorrect type detection
14 mercurial/chgserver.py # [attribute-error]
13 mercurial/chgserver.py # [attribute-error]
15 mercurial/cmdutil.py # No attribute 'markcopied' on mercurial.context.filectx [attribute-error]
14 mercurial/cmdutil.py # No attribute 'markcopied' on mercurial.context.filectx [attribute-error]
16 mercurial/context.py # many [attribute-error]
15 mercurial/context.py # many [attribute-error]
17 mercurial/copies.py # No attribute 'items' on None [attribute-error]
16 mercurial/copies.py # No attribute 'items' on None [attribute-error]
18 mercurial/crecord.py # tons of [attribute-error], [module-attr]
17 mercurial/crecord.py # tons of [attribute-error], [module-attr]
19 mercurial/debugcommands.py # [wrong-arg-types]
18 mercurial/debugcommands.py # [wrong-arg-types]
20 mercurial/dispatch.py # initstdio: No attribute ... on TextIO [attribute-error]
19 mercurial/dispatch.py # initstdio: No attribute ... on TextIO [attribute-error]
21 mercurial/exchange.py # [attribute-error]
20 mercurial/exchange.py # [attribute-error]
22 mercurial/hgweb/hgweb_mod.py # [attribute-error], [name-error], [wrong-arg-types]
21 mercurial/hgweb/hgweb_mod.py # [attribute-error], [name-error], [wrong-arg-types]
23 mercurial/hgweb/server.py # [attribute-error], [name-error], [module-attr]
22 mercurial/hgweb/server.py # [attribute-error], [name-error], [module-attr]
24 mercurial/hgweb/webcommands.py # [missing-parameter]
23 mercurial/hgweb/webcommands.py # [missing-parameter]
25 mercurial/hgweb/wsgicgi.py # confused values in os.environ
24 mercurial/hgweb/wsgicgi.py # confused values in os.environ
26 mercurial/httppeer.py # [attribute-error], [wrong-arg-types]
25 mercurial/httppeer.py # [attribute-error], [wrong-arg-types]
27 mercurial/interfaces # No attribute 'capabilities' on peer [attribute-error]
26 mercurial/interfaces # No attribute 'capabilities' on peer [attribute-error]
28 mercurial/keepalive.py # [attribute-error]
27 mercurial/keepalive.py # [attribute-error]
29 mercurial/localrepo.py # [attribute-error]
28 mercurial/localrepo.py # [attribute-error]
30 mercurial/lsprof.py # unguarded import
29 mercurial/lsprof.py # unguarded import
31 mercurial/manifest.py # [unsupported-operands], [wrong-arg-types]
30 mercurial/manifest.py # [unsupported-operands], [wrong-arg-types]
32 mercurial/minirst.py # [unsupported-operands], [attribute-error]
31 mercurial/minirst.py # [unsupported-operands], [attribute-error]
33 mercurial/patch.py # [wrong-arg-types]
32 mercurial/patch.py # [wrong-arg-types]
34 mercurial/pure/osutil.py # [invalid-typevar], [not-callable]
33 mercurial/pure/osutil.py # [invalid-typevar], [not-callable]
35 mercurial/pure/parsers.py # [attribute-error]
34 mercurial/pure/parsers.py # [attribute-error]
36 mercurial/pycompat.py # bytes vs str issues
35 mercurial/pycompat.py # bytes vs str issues
37 mercurial/repoview.py # [attribute-error]
36 mercurial/repoview.py # [attribute-error]
38 mercurial/sslutil.py # [attribute-error]
37 mercurial/sslutil.py # [attribute-error]
39 mercurial/statprof.py # bytes vs str on TextIO.write() [wrong-arg-types]
38 mercurial/statprof.py # bytes vs str on TextIO.write() [wrong-arg-types]
40 mercurial/testing/storage.py # tons of [attribute-error]
39 mercurial/testing/storage.py # tons of [attribute-error]
41 mercurial/ui.py # [attribute-error], [wrong-arg-types]
40 mercurial/ui.py # [attribute-error], [wrong-arg-types]
42 mercurial/unionrepo.py # ui, svfs, unfiltered [attribute-error]
41 mercurial/unionrepo.py # ui, svfs, unfiltered [attribute-error]
43 mercurial/util.py # [attribute-error], [wrong-arg-count]
42 mercurial/util.py # [attribute-error], [wrong-arg-count]
44 mercurial/utils/procutil.py # [attribute-error], [module-attr], [bad-return-type]
43 mercurial/utils/procutil.py # [attribute-error], [module-attr], [bad-return-type]
45 mercurial/utils/stringutil.py # [module-attr], [wrong-arg-count]
44 mercurial/utils/stringutil.py # [module-attr], [wrong-arg-count]
46 mercurial/utils/memorytop.py # not 3.6 compatible
45 mercurial/utils/memorytop.py # not 3.6 compatible
47 mercurial/win32.py # [not-callable]
46 mercurial/win32.py # [not-callable]
48 mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error]
47 mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error]
49 mercurial/wireprotoserver.py # line 253, in _availableapis: No attribute '__iter__' on Callable[[Any, Any], Any] [attribute-error]
48 mercurial/wireprotoserver.py # line 253, in _availableapis: No attribute '__iter__' on Callable[[Any, Any], Any] [attribute-error]
50 mercurial/wireprotov1peer.py # [attribute-error]
49 mercurial/wireprotov1peer.py # [attribute-error]
51 mercurial/wireprotov1server.py # BUG?: BundleValueError handler accesses subclass's attrs
50 mercurial/wireprotov1server.py # BUG?: BundleValueError handler accesses subclass's attrs
52 mercurial/wireprotov2server.py # [unsupported-operands], [attribute-error]
51 mercurial/wireprotov2server.py # [unsupported-operands], [attribute-error]
53
52
54 TODO: use --no-cache on test server? Caching the files locally helps during
53 TODO: use --no-cache on test server? Caching the files locally helps during
55 development, but may be a hinderance for CI testing.
54 development, but may be a hinderance for CI testing.
56
55
57 $ pytype -V 3.6 --keep-going --jobs auto mercurial \
56 $ pytype -V 3.6 --keep-going --jobs auto mercurial \
58 > -x mercurial/bundlerepo.py \
57 > -x mercurial/bundlerepo.py \
59 > -x mercurial/changegroup.py \
60 > -x mercurial/chgserver.py \
58 > -x mercurial/chgserver.py \
61 > -x mercurial/cmdutil.py \
59 > -x mercurial/cmdutil.py \
62 > -x mercurial/context.py \
60 > -x mercurial/context.py \
63 > -x mercurial/copies.py \
61 > -x mercurial/copies.py \
64 > -x mercurial/crecord.py \
62 > -x mercurial/crecord.py \
65 > -x mercurial/debugcommands.py \
63 > -x mercurial/debugcommands.py \
66 > -x mercurial/dispatch.py \
64 > -x mercurial/dispatch.py \
67 > -x mercurial/exchange.py \
65 > -x mercurial/exchange.py \
68 > -x mercurial/hgweb/hgweb_mod.py \
66 > -x mercurial/hgweb/hgweb_mod.py \
69 > -x mercurial/hgweb/server.py \
67 > -x mercurial/hgweb/server.py \
70 > -x mercurial/hgweb/webcommands.py \
68 > -x mercurial/hgweb/webcommands.py \
71 > -x mercurial/hgweb/wsgicgi.py \
69 > -x mercurial/hgweb/wsgicgi.py \
72 > -x mercurial/httppeer.py \
70 > -x mercurial/httppeer.py \
73 > -x mercurial/interfaces \
71 > -x mercurial/interfaces \
74 > -x mercurial/keepalive.py \
72 > -x mercurial/keepalive.py \
75 > -x mercurial/localrepo.py \
73 > -x mercurial/localrepo.py \
76 > -x mercurial/lsprof.py \
74 > -x mercurial/lsprof.py \
77 > -x mercurial/manifest.py \
75 > -x mercurial/manifest.py \
78 > -x mercurial/minirst.py \
76 > -x mercurial/minirst.py \
79 > -x mercurial/patch.py \
77 > -x mercurial/patch.py \
80 > -x mercurial/pure/osutil.py \
78 > -x mercurial/pure/osutil.py \
81 > -x mercurial/pure/parsers.py \
79 > -x mercurial/pure/parsers.py \
82 > -x mercurial/pycompat.py \
80 > -x mercurial/pycompat.py \
83 > -x mercurial/repoview.py \
81 > -x mercurial/repoview.py \
84 > -x mercurial/sslutil.py \
82 > -x mercurial/sslutil.py \
85 > -x mercurial/statprof.py \
83 > -x mercurial/statprof.py \
86 > -x mercurial/testing/storage.py \
84 > -x mercurial/testing/storage.py \
87 > -x mercurial/thirdparty \
85 > -x mercurial/thirdparty \
88 > -x mercurial/ui.py \
86 > -x mercurial/ui.py \
89 > -x mercurial/unionrepo.py \
87 > -x mercurial/unionrepo.py \
90 > -x mercurial/utils/procutil.py \
88 > -x mercurial/utils/procutil.py \
91 > -x mercurial/utils/stringutil.py \
89 > -x mercurial/utils/stringutil.py \
92 > -x mercurial/utils/memorytop.py \
90 > -x mercurial/utils/memorytop.py \
93 > -x mercurial/win32.py \
91 > -x mercurial/win32.py \
94 > -x mercurial/wireprotoframing.py \
92 > -x mercurial/wireprotoframing.py \
95 > -x mercurial/wireprotoserver.py \
93 > -x mercurial/wireprotoserver.py \
96 > -x mercurial/wireprotov1peer.py \
94 > -x mercurial/wireprotov1peer.py \
97 > -x mercurial/wireprotov1server.py \
95 > -x mercurial/wireprotov1server.py \
98 > -x mercurial/wireprotov2server.py \
96 > -x mercurial/wireprotov2server.py \
99 > > $TESTTMP/pytype-output.txt || cat $TESTTMP/pytype-output.txt
97 > > $TESTTMP/pytype-output.txt || cat $TESTTMP/pytype-output.txt
100
98
101 Only show the results on a failure, because the output on success is also
99 Only show the results on a failure, because the output on success is also
102 voluminous and variable.
100 voluminous and variable.
General Comments 0
You need to be logged in to leave comments. Login now