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