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